fix(cubefs): round-trip full record envelope through DaemonBackend + gate hardening

- DaemonBackend::put/get now round-trip the FULL record envelope (header+body)
  verbatim via rawput/rawget instead of stripping to a bare body. Before this,
  a socket-backed mount wrote the bare body but get returned it as if it were a
  record, breaking the FUSE read-back path (get_record could not decode it).
  This diverged from the in-memory backend and silently corrupted reads.
- Cargo fmt --check now passes (was failing; the committed tree was never a
  clean ./check, which is why the durable mount could regress undetected).
- Fix two new clippy lints (manual_is_multiple_of) in cubefs/backend.rs and
  cubesys/commands.rs so the -D warnings gate is green.
- Daemon-backed integration tests (cubefs_daemon_smoke, daemon_backend_smoke)
  now use the real CubeStore<DaemonBackend> record path and are #[ignore]d so
  the default gate (no daemon) stays green, while ./check daemon spins up a live
  cube-server and runs them via --ignored. This makes the durable-socket
  contract an actual enforced test, not a manual ad-hoc script.
This commit is contained in:
CUBELinux-2
2026-08-13 05:58:12 -04:00
parent 8f9e7e0025
commit 0300def300
6 changed files with 138 additions and 42 deletions
+11 -5
View File
@@ -87,11 +87,14 @@ impl DaemonBackend {
impl CubeBackend for DaemonBackend {
fn put(&mut self, key: Czyx, value: Vec<u8>) {
// `value` is the FULL record envelope (header bytes + body) emitted by
// `CubeStore::put_record` — NOT a bare body. We must store it verbatim
// so that `get` returns an identical envelope and the FUSE read path
// (`get_record`) can split header/body back out. Stripping the header
// here would make the socket backend diverge from the in-memory backend
// and break the round-trip.
let hex = to_hex(&value);
let cmd = format!(
"rawput {} {} {} {} {}",
key.c, key.z, key.y, key.x, hex
);
let cmd = format!("rawput {} {} {} {} {}", key.c, key.z, key.y, key.x, hex);
// A failed durability write is reported via stderr; the FUSE layer
// surfaces the prior successful state to the kernel. We do not panic
// here because an unreachable daemon should not crash the mount — it
@@ -102,6 +105,9 @@ impl CubeBackend for DaemonBackend {
}
fn get(&self, key: &Czyx) -> Option<Vec<u8>> {
// Returns the SAME full envelope that `put` stored. The daemon's
// `get_raw` is the exact backend bytes (header + body), which is what
// `CubeStore::get_record` expects to decode.
let cmd = format!("rawget {} {} {} {}", key.c, key.z, key.y, key.x);
match self.rpc(&cmd) {
Ok(resp) => {
@@ -197,7 +203,7 @@ fn to_hex(b: &[u8]) -> String {
}
fn from_hex(s: &str) -> Option<Vec<u8>> {
if s.len() % 2 != 0 {
if !s.len().is_multiple_of(2) {
return None;
}
let bytes = s.as_bytes();
+2 -2
View File
@@ -49,13 +49,13 @@ fn main() -> ExitCode {
Some(path) => {
eprintln!("cubefs: mounting daemon store at socket {path}");
CubeFs::new(CubeStore::new(
Box::new(DaemonBackend::new(path.clone())) as Box<dyn CubeBackend + Send + Sync>,
Box::new(DaemonBackend::new(path.clone())) as Box<dyn CubeBackend + Send + Sync>
))
}
None => {
eprintln!("cubefs: mounting IN-MEMORY store (writes are NOT durable; use --socket PATH to mount the daemon's store)");
CubeFs::new(CubeStore::new(
Box::new(HashBackend::new()) as Box<dyn CubeBackend + Send + Sync>,
Box::new(HashBackend::new()) as Box<dyn CubeBackend + Send + Sync>
))
}
};