diff --git a/Cargo.lock b/Cargo.lock index 95301be92..c7b441ee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -936,7 +936,7 @@ dependencies = [ [[package]] name = "ramshared-agent" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "ramshared-broker", "serde", @@ -945,14 +945,14 @@ dependencies = [ [[package]] name = "ramshared-block" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "ramshared-vram", ] [[package]] name = "ramshared-broker" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "serde", "serde_json", @@ -960,7 +960,7 @@ dependencies = [ [[package]] name = "ramshared-cli" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "libc", "ramshared-cuda", @@ -974,7 +974,7 @@ dependencies = [ [[package]] name = "ramshared-config" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "serde", "serde_path_to_error", @@ -983,7 +983,7 @@ dependencies = [ [[package]] name = "ramshared-cuda" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "cuda-async", "cuda-core", @@ -993,22 +993,22 @@ dependencies = [ [[package]] name = "ramshared-dxg" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "libc", ] [[package]] name = "ramshared-integrity" -version = "0.14.1" # x-release-please-version +version = "0.14.1" [[package]] name = "ramshared-tier" -version = "0.14.1" # x-release-please-version +version = "0.14.1" [[package]] name = "ramshared-uring" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "io-uring", "libc", @@ -1016,11 +1016,11 @@ dependencies = [ [[package]] name = "ramshared-vram" -version = "0.14.1" # x-release-please-version +version = "0.14.1" [[package]] name = "ramshared-vulkan" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "ash", "ramshared-vram", @@ -1028,7 +1028,7 @@ dependencies = [ [[package]] name = "ramshared-winbroker" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "ramshared-broker", "serde", @@ -1041,7 +1041,7 @@ dependencies = [ [[package]] name = "ramshared-winsvc" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "base64", "ramshared-block", @@ -1059,7 +1059,7 @@ dependencies = [ [[package]] name = "ramshared-wsl2d" -version = "0.14.1" # x-release-please-version +version = "0.14.1" dependencies = [ "ramshared-block", "ramshared-broker", diff --git a/crates/ramshared-block/src/isolated_origin.rs b/crates/ramshared-block/src/isolated_origin.rs index 482d1607d..51adad951 100644 --- a/crates/ramshared-block/src/isolated_origin.rs +++ b/crates/ramshared-block/src/isolated_origin.rs @@ -235,7 +235,7 @@ pub struct AuthoritativeOriginBackend { impl AuthoritativeOriginBackend { pub fn new(origin: O, cache: C, size: u64, block: u32) -> Result { if size == 0 || block == 0 || !size.is_multiple_of(block as u64) { - return Err(IoError("invalid authoritative origin geometry".into())); + return Err(IoError::Fatal("invalid authoritative origin geometry".into())); } Ok(Self { origin, @@ -275,7 +275,7 @@ impl AuthoritativeOriginBackend { self.telemetry.releases = self.telemetry.releases.saturating_add(1); Ok(0) } - CacheMutation::Failed => Err(IoError( + CacheMutation::Failed => Err(IoError::Fatal( "cache release acknowledgement was unavailable".into(), )), } @@ -313,14 +313,14 @@ impl AuthoritativeOriginBackend { .checked_add(len as u64) .filter(|end| *end <= self.size) .map(|_| ()) - .ok_or_else(|| IoError("authoritative origin I/O is out of range".into())) + .ok_or_else(|| IoError::Fatal("authoritative origin I/O is out of range".into())) } fn require_ready_origin(&self) -> Result<(), IoError> { if self.origin_state == OriginState::Ready { Ok(()) } else { - Err(IoError( + Err(IoError::Fatal( "origin authority is unavailable pending three read+sync probes".into(), )) } @@ -618,7 +618,7 @@ mod tests { impl OriginStorage for FaultOrigin { fn read_at(&mut self, offset: u64, destination: &mut [u8]) -> Result { if self.0.fail_read.get() { - return Err(IoError("fixture origin read failure".into())); + return Err(IoError::Fatal("fixture origin read failure".into())); } let start = offset as usize; destination.copy_from_slice(&self.0.bytes.borrow()[start..start + destination.len()]); @@ -627,7 +627,7 @@ mod tests { fn write_at(&mut self, offset: u64, data: &[u8]) -> Result { if self.0.fail_write.get() { - return Err(IoError("fixture origin write failure".into())); + return Err(IoError::Fatal("fixture origin write failure".into())); } let start = offset as usize; self.0.bytes.borrow_mut()[start..start + data.len()].copy_from_slice(data); @@ -636,7 +636,7 @@ mod tests { fn sync_data(&mut self) -> Result<(), IoError> { if self.0.fail_sync.get() { - return Err(IoError("fixture origin sync failure".into())); + return Err(IoError::Fatal("fixture origin sync failure".into())); } self.0.syncs.set(self.0.syncs.get().saturating_add(1)); Ok(()) diff --git a/crates/ramshared-block/src/lib.rs b/crates/ramshared-block/src/lib.rs index c0cc39ad0..d8842f95f 100644 --- a/crates/ramshared-block/src/lib.rs +++ b/crates/ramshared-block/src/lib.rs @@ -56,3 +56,6 @@ mod tests { assert_eq!(DEFAULT_CHUNK_MIB, 128); } } + +#[cfg(test)] +mod red_test; diff --git a/crates/ramshared-block/src/origin_cache.rs b/crates/ramshared-block/src/origin_cache.rs index d78db02c5..e6fa85abc 100644 --- a/crates/ramshared-block/src/origin_cache.rs +++ b/crates/ramshared-block/src/origin_cache.rs @@ -29,14 +29,14 @@ pub trait OriginStorage { while !buf.is_empty() { let read = self.read_at(off, buf)?; if read == 0 { - return Err(IoError("origin read made no progress".into())); + return Err(IoError::Fatal("origin read made no progress".into())); } if read > buf.len() { - return Err(IoError("origin read exceeded requested length".into())); + return Err(IoError::Fatal("origin read exceeded requested length".into())); } off = off .checked_add(read as u64) - .ok_or_else(|| IoError("origin read offset overflow".into()))?; + .ok_or_else(|| IoError::Fatal("origin read offset overflow".into()))?; buf = &mut buf[read..]; } Ok(()) @@ -46,14 +46,14 @@ pub trait OriginStorage { while !data.is_empty() { let written = self.write_at(off, data)?; if written == 0 { - return Err(IoError("origin write made no progress".into())); + return Err(IoError::Fatal("origin write made no progress".into())); } if written > data.len() { - return Err(IoError("origin write exceeded requested length".into())); + return Err(IoError::Fatal("origin write exceeded requested length".into())); } off = off .checked_add(written as u64) - .ok_or_else(|| IoError("origin write offset overflow".into()))?; + .ok_or_else(|| IoError::Fatal("origin write offset overflow".into()))?; data = &data[written..]; } Ok(()) @@ -79,18 +79,18 @@ impl OriginStorage for FileOrigin { { self.file .read_at(buf, off) - .map_err(|error| IoError(error.to_string())) + .map_err(|error| IoError::Fatal(error.to_string())) } #[cfg(windows)] { self.file .seek_read(buf, off) - .map_err(|error| IoError(error.to_string())) + .map_err(|error| IoError::Fatal(error.to_string())) } #[cfg(not(any(unix, windows)))] { let _ = (off, buf); - Err(IoError("unsupported platform for FileOrigin".into())) + Err(IoError::Fatal("unsupported platform for FileOrigin".into())) } } @@ -99,25 +99,25 @@ impl OriginStorage for FileOrigin { { self.file .write_at(data, off) - .map_err(|error| IoError(error.to_string())) + .map_err(|error| IoError::Fatal(error.to_string())) } #[cfg(windows)] { self.file .seek_write(data, off) - .map_err(|error| IoError(error.to_string())) + .map_err(|error| IoError::Fatal(error.to_string())) } #[cfg(not(any(unix, windows)))] { let _ = (off, data); - Err(IoError("unsupported platform for FileOrigin".into())) + Err(IoError::Fatal("unsupported platform for FileOrigin".into())) } } fn sync_data(&mut self) -> Result<(), IoError> { self.file .sync_data() - .map_err(|error| IoError(error.to_string())) + .map_err(|error| IoError::Fatal(error.to_string())) } } @@ -251,7 +251,7 @@ impl<'p, P: VramProvider + 'p, O: OriginStorage> WriteThroughCacheBackend<'p, P, || !size.is_multiple_of(block as u64) || !chunk_bytes.is_multiple_of(block as u64) { - return Err(IoError("invalid origin cache geometry".into())); + return Err(IoError::Fatal("invalid origin cache geometry".into())); } let chunk_count = size.div_ceil(chunk_bytes); let mut chunks = Vec::with_capacity(chunk_count as usize); @@ -417,7 +417,7 @@ impl<'p, P: VramProvider + 'p, O: OriginStorage> WriteThroughCacheBackend<'p, P, off.checked_add(len as u64) .filter(|end| *end <= self.size) .map(|_| ()) - .ok_or_else(|| IoError("origin cache I/O is out of range".into())) + .ok_or_else(|| IoError::Fatal("origin cache I/O is out of range".into())) } fn valid_block_count(&self) -> u64 { @@ -522,7 +522,7 @@ impl<'p, P: VramProvider + 'p, O: OriginStorage> WriteThroughCacheBackend<'p, P, if self.origin_state == OriginState::Ready { Ok(()) } else { - Err(IoError( + Err(IoError::Fatal( "origin authority is unavailable pending three read+sync probes".into(), )) } @@ -771,7 +771,7 @@ mod tests { fn read_at(&mut self, off: u64, buf: &mut [u8]) -> Result { self.events.borrow_mut().push("origin_read"); if self.fail_read.get() { - return Err(IoError("injected origin read failure".into())); + return Err(IoError::Fatal("injected origin read failure".into())); } let bytes = self.bytes.borrow(); let start = off as usize; @@ -783,11 +783,11 @@ mod tests { fn write_at(&mut self, off: u64, data: &[u8]) -> Result { self.events.borrow_mut().push("origin_write"); if self.fail_write.get() { - return Err(IoError("injected origin write failure".into())); + return Err(IoError::Fatal("injected origin write failure".into())); } let writes_before_failure = self.writes_before_failure.get(); if writes_before_failure == 0 { - return Err(IoError("injected partial origin write failure".into())); + return Err(IoError::Fatal("injected partial origin write failure".into())); } if self.zero_write.get() { return Ok(0); @@ -803,7 +803,7 @@ mod tests { fn sync_data(&mut self) -> Result<(), IoError> { self.events.borrow_mut().push("origin_sync"); if self.fail_sync.get() { - Err(IoError("injected origin sync failure".into())) + Err(IoError::Fatal("injected origin sync failure".into())) } else { Ok(()) } @@ -1111,7 +1111,7 @@ mod tests { let error = backend.write_at(0, b"stop").unwrap_err(); - assert!(error.0.contains("no progress")); + assert!(error.to_string().contains("no progress")); assert_eq!(backend.telemetry().origin_written_bytes, 0); assert_eq!(backend.telemetry().valid_blocks, 0); } diff --git a/crates/ramshared-block/src/protocol.rs b/crates/ramshared-block/src/protocol.rs index 2e3877278..edaae07a5 100644 --- a/crates/ramshared-block/src/protocol.rs +++ b/crates/ramshared-block/src/protocol.rs @@ -35,6 +35,7 @@ pub const NBD_EIO: u32 = 5; pub const NBD_EACCES: u32 = 13; pub const NBD_EINVAL: u32 = 22; pub const NBD_ERANGE: u32 = 34; +pub const NBD_ENOSPC: u32 = 28; /// NBD commands (`type` field). #[derive(Clone, Copy, Debug, Eq, PartialEq)] diff --git a/crates/ramshared-block/src/red_test.rs b/crates/ramshared-block/src/red_test.rs new file mode 100644 index 000000000..6cd215be0 --- /dev/null +++ b/crates/ramshared-block/src/red_test.rs @@ -0,0 +1,39 @@ +use crate::protocol::{Command, NBD_ENOSPC, Request}; +use crate::request::{serve, BlockBackend, IoError}; + +struct ChaosBackend; + +impl BlockBackend for ChaosBackend { + fn size_bytes(&self) -> u64 { + 4096 + } + fn block_size(&self) -> u32 { + 4096 + } + fn read_at(&mut self, _off: u64, _buf: &mut [u8]) -> Result<(), IoError> { + Err(IoError::Retryable("chaos read network glitch".to_string())) + } + fn write_at(&mut self, _off: u64, _data: &[u8]) -> Result<(), IoError> { + Err(IoError::Retryable("chaos write network glitch".to_string())) + } + fn flush(&mut self) -> Result<(), IoError> { + Err(IoError::Retryable("chaos flush network glitch".to_string())) + } +} + +#[test] +fn retryable_error_returns_enospc() { + let mut b = ChaosBackend; + let req = Request { + flags: 0, + cmd: Command::Write, + handle: 0x42, + offset: 0, + len: 4096, + }; + let w = serve(&req, &vec![0; 4096], &mut b); + assert_eq!( + u32::from_be_bytes([w.reply[4], w.reply[5], w.reply[6], w.reply[7]]), + NBD_ENOSPC + ); +} diff --git a/crates/ramshared-block/src/request.rs b/crates/ramshared-block/src/request.rs index 2515f52e6..969986467 100644 --- a/crates/ramshared-block/src/request.rs +++ b/crates/ramshared-block/src/request.rs @@ -4,11 +4,23 @@ use crate::protocol::{Command, NBD_CMD_FLAG_FUA, Request, SIMPLE_REPLY_LEN, encode_simple_reply}; // errno in simple reply (re-exported from protocol for backward compatibility). -pub use crate::protocol::{NBD_EACCES, NBD_EINVAL, NBD_EIO, NBD_EPERM, NBD_ERANGE, NBD_OK}; +pub use crate::protocol::{NBD_EACCES, NBD_EINVAL, NBD_EIO, NBD_ENOSPC, NBD_EPERM, NBD_ERANGE, NBD_OK}; /// Storage backend error (e.g., CUDA failure in the hot path). #[derive(Debug)] -pub struct IoError(pub String); +pub enum IoError { + Fatal(String), + Retryable(String), +} + +impl std::fmt::Display for IoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + IoError::Fatal(s) => write!(f, "{}", s), + IoError::Retryable(s) => write!(f, "{}", s), + } + } +} #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct WriteOptions { @@ -54,7 +66,8 @@ pub struct ServeOutcome { fn errno_of(r: Result<(), IoError>) -> u32 { match r { Ok(()) => NBD_OK, - Err(_) => NBD_EIO, + Err(IoError::Retryable(_)) => NBD_ENOSPC, + Err(IoError::Fatal(_)) => NBD_EIO, } } diff --git a/crates/ramshared-block/src/sparse_vram.rs b/crates/ramshared-block/src/sparse_vram.rs index c998955d5..7016ac177 100644 --- a/crates/ramshared-block/src/sparse_vram.rs +++ b/crates/ramshared-block/src/sparse_vram.rs @@ -122,21 +122,21 @@ impl<'p, P: VramProvider + 'p> SparseVramBackend<'p, P> { pub fn new_with_config(provider: &'p P, config: SparseVramConfig<'p>) -> Result { if config.capacity == 0 { - return Err(IoError("sparse: capacity 0".into())); + return Err(IoError::Fatal("sparse: capacity 0".into())); } if config.chunk_bytes == 0 || !config .chunk_bytes .is_multiple_of(u64::from(config.block_size)) { - return Err(IoError(format!( + return Err(IoError::Fatal(format!( "sparse: chunk_bytes={} must be >0 and multiple of block_size={}", config.chunk_bytes, config.block_size ))); } let n = config.capacity.div_ceil(config.chunk_bytes); if n > 1_000_000 { - return Err(IoError(format!("sparse: too many chunks ({n})"))); + return Err(IoError::Fatal(format!("sparse: too many chunks ({n})"))); } // Cap commit to capacity; optional env can lower further. let commit_cap = config @@ -239,7 +239,7 @@ impl<'p, P: VramProvider + 'p> SparseVramBackend<'p, P> { fn ensure_live(&mut self, idx: usize) -> Result<(), IoError> { let Some(chunk) = self.chunks.get(idx) else { - return Err(IoError(format!( + return Err(IoError::Fatal(format!( "sparse page table oob idx={idx} len={}", self.chunks.len() ))); @@ -253,13 +253,13 @@ impl<'p, P: VramProvider + 'p> SparseVramBackend<'p, P> { && let Err(message) = gate.allow_commit(self.committed_bytes(), self.chunk_bytes) { self.budget_refuses = self.budget_refuses.saturating_add(1); - return Err(IoError(format!( + return Err(IoError::Fatal(format!( "sparse host budget constrained before allocation: {message}" ))); } if next_commit > self.commit_cap_bytes { self.floor_refuses = self.floor_refuses.saturating_add(1); - return Err(IoError(format!( + return Err(IoError::Fatal(format!( "sparse commit_cap: committed would be {} MiB > cap {} MiB (capacity {} MiB); \ refusing the write because swap fallback is not guaranteed", next_commit >> 20, @@ -273,7 +273,7 @@ impl<'p, P: VramProvider + 'p> SparseVramBackend<'p, P> { let need = self.reserve_floor_bytes.saturating_add(self.chunk_bytes); if free < need { self.floor_refuses = self.floor_refuses.saturating_add(1); - return Err(IoError(format!( + return Err(IoError::Fatal(format!( "sparse free-floor: free {} MiB < reserve+chunk {} MiB — refuse alloc \ (protect GPU)", free >> 20, @@ -283,7 +283,7 @@ impl<'p, P: VramProvider + 'p> SparseVramBackend<'p, P> { } Err(e) => { self.alloc_fails = self.alloc_fails.saturating_add(1); - return Err(IoError(format!("sparse mem_info: {e}"))); + return Err(IoError::Fatal(format!("sparse mem_info: {e}"))); } } let len = self.chunk_bytes as usize; @@ -292,12 +292,12 @@ impl<'p, P: VramProvider + 'p> SparseVramBackend<'p, P> { Ok(m) => m, Err(e) => { self.alloc_fails = self.alloc_fails.saturating_add(1); - return Err(IoError(format!("sparse alloc chunk {idx}: {e}"))); + return Err(IoError::Fatal(format!("sparse alloc chunk {idx}: {e}"))); } }; - m.zero().map_err(|e| IoError(e.to_string()))?; + m.zero().map_err(|e| IoError::Fatal(e.to_string()))?; let Some(chunk) = self.chunks.get_mut(idx) else { - return Err(IoError(format!( + return Err(IoError::Fatal(format!( "sparse page table oob idx={idx} len={}", self.chunks.len() ))); @@ -308,7 +308,7 @@ impl<'p, P: VramProvider + 'p> SparseVramBackend<'p, P> { fn chunk_index(&self, off: u64) -> Result { if off >= self.capacity { - return Err(IoError(format!( + return Err(IoError::Fatal(format!( "sparse oob off={off} capacity={}", self.capacity ))); @@ -334,7 +334,7 @@ impl<'p, P: VramProvider + 'p> BlockBackend for SparseVramBackend<'p, P> { .checked_add(buf.len() as u64) .filter(|&e| e <= self.capacity) .ok_or_else(|| { - IoError(format!( + IoError::Fatal(format!( "sparse read oob off={off} len={} cap={}", buf.len(), self.capacity @@ -350,14 +350,14 @@ impl<'p, P: VramProvider + 'p> BlockBackend for SparseVramBackend<'p, P> { let room = (self.chunk_bytes as usize).saturating_sub(rel); let n = (buf.len() - done).min(room); let Some(chunk) = self.chunks.get(idx) else { - return Err(IoError(format!( + return Err(IoError::Fatal(format!( "sparse page table oob idx={idx} len={}", self.chunks.len() ))); }; if let Some(m) = &chunk.mem { m.read_at(rel as u64, &mut buf[done..done + n]) - .map_err(|e: VramError| IoError(e.to_string()))?; + .map_err(|e: VramError| IoError::Fatal(e.to_string()))?; } else { buf[done..done + n].fill(0); } @@ -374,7 +374,7 @@ impl<'p, P: VramProvider + 'p> BlockBackend for SparseVramBackend<'p, P> { .checked_add(data.len() as u64) .filter(|&e| e <= self.capacity) .ok_or_else(|| { - IoError(format!( + IoError::Fatal(format!( "sparse write oob off={off} len={} cap={}", data.len(), self.capacity @@ -392,7 +392,7 @@ impl<'p, P: VramProvider + 'p> BlockBackend for SparseVramBackend<'p, P> { let room = (self.chunk_bytes as usize).saturating_sub(rel); let n = (data.len() - done).min(room); let Some(chunk) = self.chunks.get_mut(idx) else { - return Err(IoError(format!( + return Err(IoError::Fatal(format!( "sparse page table oob idx={idx} len={}", self.chunks.len() ))); @@ -400,9 +400,9 @@ impl<'p, P: VramProvider + 'p> BlockBackend for SparseVramBackend<'p, P> { let m = chunk .mem .as_mut() - .ok_or_else(|| IoError("sparse: mem missing after ensure".into()))?; + .ok_or_else(|| IoError::Fatal("sparse: mem missing after ensure".into()))?; m.write_at(rel as u64, &data[done..done + n]) - .map_err(|e: VramError| IoError(e.to_string()))?; + .map_err(|e: VramError| IoError::Fatal(e.to_string()))?; chunk.written = true; chunk.last_write = Some(now); @@ -550,10 +550,10 @@ mod tests { be.chunks.pop(); let off = 3 * 256 * 1024; let err_read = be.read_at(off, &mut [0u8; 4096]).unwrap_err(); - assert!(err_read.0.contains("sparse page table oob idx=")); + assert!(err_read.to_string().contains("sparse page table oob idx=")); let err_write = be.write_at(off, &[0u8; 4096]).unwrap_err(); - assert!(err_write.0.contains("sparse page table oob idx=")); + assert!(err_write.to_string().contains("sparse page table oob idx=")); } #[test] @@ -632,7 +632,7 @@ mod tests { p.fail_next.set(true); let mut be = SparseVramBackend::new(&p, 1024 * 1024, 256 * 1024, 4096).unwrap(); let err = be.write_at(0, &[1u8; 4096]).unwrap_err(); - assert!(err.0.contains("alloc") || err.0.contains("fail")); + assert!(err.to_string().contains("alloc") || err.to_string().contains("fail")); assert_eq!(be.alloc_fails, 1); } @@ -652,7 +652,7 @@ mod tests { .unwrap(); be.write_at(0, &[1u8; 4096]).unwrap(); let err = be.write_at(chunk, &[2u8; 4096]).unwrap_err(); - assert!(err.0.contains("commit_cap"), "{err:?}"); + assert!(err.to_string().contains("commit_cap"), "{err:?}"); assert_eq!(be.chunks_live(), 1); assert!(be.floor_refuses >= 1); } @@ -677,7 +677,7 @@ mod tests { ) .unwrap(); let error = be.write_at(0, &[1u8; 4096]).unwrap_err(); - assert!(error.0.contains("WDDM constrained"), "{error:?}"); + assert!(error.to_string().contains("WDDM constrained"), "{error:?}"); assert_eq!(p.allocs.get(), 0); assert_eq!(be.budget_refuses, 1); } @@ -704,8 +704,8 @@ mod tests { let mut be = SparseVramBackend::new(&p, 1024 * 1024, 256 * 1024, 4096).unwrap(); let err = be.ensure_live(9999).expect_err("should return IoError"); assert!( - err.0.contains("sparse page table oob idx=9999") - || err.0.contains("exceeds physical map len") + err.to_string().contains("sparse page table oob idx=9999") + || err.to_string().contains("exceeds physical map len") ); } @@ -767,7 +767,7 @@ mod tests { .unwrap(); let err = be.write_at(0, &[1u8; 4096]).unwrap_err(); assert!( - err.0.contains("free-floor") || err.0.contains("floor"), + err.to_string().contains("free-floor") || err.to_string().contains("floor"), "{err:?}" ); } @@ -792,7 +792,7 @@ mod tests { SparseVramBackend::new_with_limits(&p, 1024 * 1024, 256 * 1024, 4096, 0, None).unwrap(); let err = be.write_at(0, &[1u8; 4096]).unwrap_err(); assert!( - err.0.contains("mem_info") || err.0.contains("no gpu"), + err.to_string().contains("mem_info") || err.to_string().contains("no gpu"), "{err:?}" ); assert_eq!(be.alloc_fails, 1); diff --git a/crates/ramshared-block/src/vram_backend.rs b/crates/ramshared-block/src/vram_backend.rs index 294377c97..0110dee4b 100644 --- a/crates/ramshared-block/src/vram_backend.rs +++ b/crates/ramshared-block/src/vram_backend.rs @@ -53,13 +53,13 @@ impl BlockBackend for VramBackend { fn read_at(&mut self, off: u64, buf: &mut [u8]) -> Result<(), IoError> { self.mem .read_at(off, buf) - .map_err(|e| IoError(e.to_string())) + .map_err(|e| IoError::Fatal(e.to_string())) } fn write_at(&mut self, off: u64, data: &[u8]) -> Result<(), IoError> { self.mem .write_at(off, data) - .map_err(|e| IoError(e.to_string())) + .map_err(|e| IoError::Fatal(e.to_string())) } fn flush(&mut self) -> Result<(), IoError> { diff --git a/crates/ramshared-winsvc/src/driver_link.rs b/crates/ramshared-winsvc/src/driver_link.rs index ec0cd5f42..7ac9716d4 100644 --- a/crates/ramshared-winsvc/src/driver_link.rs +++ b/crates/ramshared-winsvc/src/driver_link.rs @@ -554,8 +554,8 @@ mod tests { Ok(()) } fn write_at(&mut self, off: u64, data: &[u8]) -> Result<(), IoError> { - *self.writes.lock().map_err(|e| IoError(e.to_string()))? += 1; - *self.last_write.lock().map_err(|e| IoError(e.to_string()))? = data.to_vec(); + *self.writes.lock().map_err(|e| IoError::Fatal(e.to_string()))? += 1; + *self.last_write.lock().map_err(|e| IoError::Fatal(e.to_string()))? = data.to_vec(); let o = off as usize; self.data[o..o + data.len()].copy_from_slice(data); Ok(()) diff --git a/crates/ramshared-wsl2d/src/backend.rs b/crates/ramshared-wsl2d/src/backend.rs index c0c3d3239..99fe9ac49 100644 --- a/crates/ramshared-wsl2d/src/backend.rs +++ b/crates/ramshared-wsl2d/src/backend.rs @@ -46,7 +46,7 @@ impl BlockBackend for SliceView<'_, B> { let abs = self .base .checked_add(off) - .ok_or_else(|| IoError("SliceView read offset overflow".into()))?; + .ok_or_else(|| IoError::Fatal("SliceView read offset overflow".into()))?; self.inner.read_at(abs, buf) } @@ -54,7 +54,7 @@ impl BlockBackend for SliceView<'_, B> { let abs = self .base .checked_add(off) - .ok_or_else(|| IoError("SliceView write offset overflow".into()))?; + .ok_or_else(|| IoError::Fatal("SliceView write offset overflow".into()))?; self.inner.write_at(abs, data) } @@ -67,7 +67,7 @@ impl BlockBackend for SliceView<'_, B> { let abs = self .base .checked_add(off) - .ok_or_else(|| IoError("SliceView write offset overflow".into()))?; + .ok_or_else(|| IoError::Fatal("SliceView write offset overflow".into()))?; self.inner.write_at_with_options(abs, data, options) } @@ -111,7 +111,7 @@ impl BlockBackend for RamBackend { fn read_at(&mut self, off: u64, buf: &mut [u8]) -> Result<(), IoError> { let (start, end) = self .range(off, buf.len()) - .ok_or_else(|| IoError("RamBackend read out of range".into()))?; + .ok_or_else(|| IoError::Fatal("RamBackend read out of range".into()))?; buf.copy_from_slice(&self.data[start..end]); Ok(()) } @@ -119,7 +119,7 @@ impl BlockBackend for RamBackend { fn write_at(&mut self, off: u64, data: &[u8]) -> Result<(), IoError> { let (start, end) = self .range(off, data.len()) - .ok_or_else(|| IoError("RamBackend write out of range".into()))?; + .ok_or_else(|| IoError::Fatal("RamBackend write out of range".into()))?; self.data[start..end].copy_from_slice(data); Ok(()) } diff --git a/crates/ramshared-wsl2d/src/main.rs b/crates/ramshared-wsl2d/src/main.rs index 05f0b1cb4..48692a0c2 100644 --- a/crates/ramshared-wsl2d/src/main.rs +++ b/crates/ramshared-wsl2d/src/main.rs @@ -2928,7 +2928,7 @@ fn run_nbd_with_startup( let mut backend: Be<'_, P> = if let Some(origin) = origin { let cache = AuthoritativeOriginBackend::new(origin, DisabledCache, size, BLOCK_SIZE) - .map_err(|error| error.0)?; + .map_err(|error| error.to_string())?; eprintln!( "[ramsharedd] mode=authoritative-origin logical={} MiB cache=UNAVAILABLE \ isolation=bounded-worker-required", @@ -2952,7 +2952,7 @@ fn run_nbd_with_startup( budget_gate, }, ) - .map_err(|e| e.0)?; + .map_err(|e| e.to_string())?; eprintln!( "[ramsharedd] VRAM mode=sparse capacity={} MiB chunk={} MiB \ commit_cap={} MiB reserve_floor={} MiB committed=0 (ondemand+safety)", @@ -3268,7 +3268,7 @@ fn run_nbd_with_startup( n >> 20, sp.chunks_live() ), - Err(e) => eprintln!("[ramsharedd] sparse reclaim err: {}", e.0), + Err(e) => eprintln!("[ramsharedd] sparse reclaim err: {}", e), } } @@ -3299,7 +3299,7 @@ fn run_nbd_with_startup( Some(Err(error)) => { eprintln!( "[ramsharedd] control cache release was not acknowledged: {}", - error.0 + error ); } None => {} @@ -3501,7 +3501,7 @@ fn run_nbd_with_startup( } Be::Origin(b) => { let released = b.release_cache().map_err(|error| { - format!("origin cache release was not acknowledged: {}", error.0) + format!("origin cache release was not acknowledged: {}", error) })?; eprintln!( "[ramsharedd] stopped (released {} MiB clean origin cache)", @@ -5402,7 +5402,7 @@ mod tests { impl ramshared_block::OriginStorage for TestOrigin { fn read_at(&mut self, off: u64, buf: &mut [u8]) -> Result { if self.fail.get() { - return Err(ramshared_block::IoError("injected origin failure".into())); + return Err(ramshared_block::IoError::Fatal("injected origin failure".into())); } let start = off as usize; let count = buf.len().min(self.bytes.len().saturating_sub(start)); @@ -5412,7 +5412,7 @@ mod tests { fn write_at(&mut self, off: u64, data: &[u8]) -> Result { if self.fail.get() { - return Err(ramshared_block::IoError("injected origin failure".into())); + return Err(ramshared_block::IoError::Fatal("injected origin failure".into())); } let start = off as usize; let count = data.len().min(self.bytes.len().saturating_sub(start)); @@ -5422,7 +5422,7 @@ mod tests { fn sync_data(&mut self) -> Result<(), ramshared_block::IoError> { if self.fail.get() { - Err(ramshared_block::IoError("injected origin failure".into())) + Err(ramshared_block::IoError::Fatal("injected origin failure".into())) } else { Ok(()) } @@ -9371,14 +9371,14 @@ mod tests { } fn read_at(&mut self, _off: u64, _buf: &mut [u8]) -> Result<(), ramshared_block::IoError> { if self.fail_io.load(Ordering::SeqCst) { - Err(ramshared_block::IoError("simulated I/O failure".into())) + Err(ramshared_block::IoError::Fatal("simulated I/O failure".into())) } else { Ok(()) } } fn write_at(&mut self, _off: u64, _data: &[u8]) -> Result<(), ramshared_block::IoError> { if self.fail_io.load(Ordering::SeqCst) { - Err(ramshared_block::IoError("simulated I/O failure".into())) + Err(ramshared_block::IoError::Fatal("simulated I/O failure".into())) } else { Ok(()) } diff --git a/crates/ramshared-wsl2d/tests/ublk_server.rs b/crates/ramshared-wsl2d/tests/ublk_server.rs index 88fb0a8b0..e27f4d275 100644 --- a/crates/ramshared-wsl2d/tests/ublk_server.rs +++ b/crates/ramshared-wsl2d/tests/ublk_server.rs @@ -110,7 +110,7 @@ impl BlockBackend for FailingBackend { fn read_at(&mut self, _off: u64, _buf: &mut [u8]) -> Result<(), IoError> { if self.fail_read { - Err(IoError("simulated read failure".into())) + Err(IoError::Fatal("simulated read failure".into())) } else { Ok(()) } @@ -118,7 +118,7 @@ impl BlockBackend for FailingBackend { fn write_at(&mut self, _off: u64, _data: &[u8]) -> Result<(), IoError> { if self.fail_write { - Err(IoError("simulated write failure".into())) + Err(IoError::Fatal("simulated write failure".into())) } else { Ok(()) } @@ -126,7 +126,7 @@ impl BlockBackend for FailingBackend { fn flush(&mut self) -> Result<(), IoError> { if self.fail_flush { - Err(IoError("simulated flush failure".into())) + Err(IoError::Fatal("simulated flush failure".into())) } else { Ok(()) } diff --git a/fix_all.py b/fix_all.py new file mode 100644 index 000000000..99f01daaa --- /dev/null +++ b/fix_all.py @@ -0,0 +1,19 @@ +import glob +import re +import os + +with open("crates/ramshared-block/src/protocol.rs", "r") as f: + text = f.read() + +# Make sure we only replace the FIRST occurrence to prevent duplicating +# if this script is run multiple times by accident. +text = re.sub( + r"pub const NBD_ERANGE: u32 = 34;", + r"pub const NBD_ERANGE: u32 = 34;\npub const NBD_ENOSPC: u32 = 28;", + text, + count=1 +) + +# Wait, the problem is that git checkout crates/ramshared-block/src/protocol.rs +# doesn't remove the extra lines if it was already committed, but we ran reset_all so it should be fine. +# Let's clean the file manually.