fix(cubefs): make FUSE mount a durable view of cube-server daemon store

The cubefs-mount --socket path was never actually built: CubeFs<B> is
generic, so the --socket (DaemonBackend) and default (HashBackend) arms
of the match were incompatible types (E0308), and --features mount failed
to compile. The running binary was therefore the in-memory build, so
--socket was silently ignored and every FUSE write went to RAM and never
reached the daemon (rawget/rawkeys returned none / 0 keys, WAL stayed 0).

Fix: type-erase the backend. Add  (forwards to inner) in cubestore, and build
 in cubefs-mount via
Box::new(DaemonBackend::new(p)) / Box::new(HashBackend::new()). Keeps
cubefs free of a cubesys dep (acyclic graph).

Verified end-to-end on host (shared code path as the VM): a FUSE write
via  lands in the daemon store (rawget returns the
record, 5 keys present, WAL grows), and survives a  of the
daemon + relaunch with the same --store (byte-identical read-back).

Also includes (from RESUME-cubefs-daemon.md): cubesys raw* command family
(rawget/rawput/rawdel/rawkeys/rawscan + parse/hex helpers) and the
DaemonBackend client + smoke tests. Report/verification docs added.

Note: VM cubefs.service still mounts in-memory (no --socket); update the
unit to  as a
follow-up so the deployed VM FUSE is durable too.
This commit is contained in:
CUBELinux-2
2026-08-13 05:40:59 -04:00
parent ad73f42e46
commit 8f9e7e0025
10 changed files with 574 additions and 13 deletions
+93
View File
@@ -557,6 +557,64 @@ impl Session {
store.delete_raw(&coord);
Ok(format!("deleted {path}"))
}
// --- Raw coordinate API (used by cubefs' socket-backed backend,
// and any client that wants to address the store by CZYX
// directly instead of by path). These mutate the SAME durable
// `ConcurrentStore` the daemon serves, so a write through here
// is immediately visible to `ls`/`stat`/FUSE and is folded
// into the WAL + checkpoint like any other write. ---
"rawget" => {
let c = parse_u8(it.next(), "rawget needs <c>")?;
let z = parse_u8(it.next(), "rawget needs <z>")?;
let y = parse_u8(it.next(), "rawget needs <y>")?;
let x = parse_u8(it.next(), "rawget needs <x>")?;
let coord = Czyx::new(c, z, y, x);
match store.get_raw(&coord) {
Some(v) => Ok(format!("ok: {}", hex_encode(&v))),
None => Ok("none".to_string()),
}
}
"rawput" => {
let c = parse_u8(it.next(), "rawput needs <c>")?;
let z = parse_u8(it.next(), "rawput needs <z>")?;
let y = parse_u8(it.next(), "rawput needs <y>")?;
let x = parse_u8(it.next(), "rawput needs <x>")?;
let hex = it
.next()
.ok_or_else(|| "rawput needs <hex-bytes>".to_string())?;
let val = hex_decode(hex)
.ok_or_else(|| "rawput: value must be hex".to_string())?;
let coord = Czyx::new(c, z, y, x);
store.put_raw(coord, val);
Ok(format!("ok: wrote {}", coord.pack_u32()))
}
"rawdel" => {
let c = parse_u8(it.next(), "rawdel needs <c>")?;
let z = parse_u8(it.next(), "rawdel needs <z>")?;
let y = parse_u8(it.next(), "rawdel needs <y>")?;
let x = parse_u8(it.next(), "rawdel needs <x>")?;
let coord = Czyx::new(c, z, y, x);
store.delete_raw(&coord);
Ok(format!("ok: deleted {}", coord.pack_u32()))
}
"rawkeys" => {
let ks: Vec<String> =
store.keys().iter().map(|k| k.pack_u32().to_string()).collect();
Ok(format!("ok: {} keys", ks.len()))
.map(|s| if ks.is_empty() { s } else { format!("{s}\n{}", ks.join(" ")) })
}
"rawscan" => {
let c = parse_u8(it.next(), "rawscan needs <c>")?;
let z = it.next().and_then(|t| t.parse::<u8>().ok());
let y = it.next().and_then(|t| t.parse::<u8>().ok());
let ks: Vec<String> = store
.scan_prefix(c, z, y)
.iter()
.map(|k| k.pack_u32().to_string())
.collect();
Ok(format!("ok: {} keys", ks.len()))
.map(|s| if ks.is_empty() { s } else { format!("{s}\n{}", ks.join(" ")) })
}
"grant" => {
// Issue a permission grant (Task 6b / PDF flags 5-19). Only an
// identified owner may grant (under --require-identity); in the
@@ -855,6 +913,41 @@ pub fn parse_coord(s: &str) -> Option<cubecoords::Czyx> {
Some(cubecoords::Czyx::new(nums[0], nums[1], nums[2], nums[3]))
}
/// Parse a single `u8` axis token: decimal (`7`) or hex (`0x07`).
fn parse_u8(t: Option<&str>, what: &str) -> Result<u8, String> {
let t = t.ok_or_else(|| what.to_string())?;
t.parse::<u8>()
.or_else(|_| u8::from_str_radix(t.trim_start_matches("0x"), 16))
.map_err(|_| format!("{what} (got '{t}')"))
}
/// Encode bytes as a lowercase hex string (used by the raw coordinate API so
/// payloads survive the text socket framing).
fn hex_encode(b: &[u8]) -> String {
let mut s = String::with_capacity(b.len() * 2);
for byte in b {
s.push_str(&format!("{byte:02x}"));
}
s
}
/// Decode a hex string into bytes. Rejects odd length / non-hex.
fn hex_decode(s: &str) -> Option<Vec<u8>> {
if s.len() % 2 != 0 {
return None;
}
let bytes = s.as_bytes();
let mut out = Vec::with_capacity(s.len() / 2);
let mut i = 0;
while i < bytes.len() {
let hi = (bytes[i] as char).to_digit(16)?;
let lo = (bytes[i + 1] as char).to_digit(16)?;
out.push(((hi << 4) | lo) as u8);
i += 2;
}
Some(out)
}
/// Parse a cubevm op name (case-insensitive) into an [`Op`]. `arg` is the
/// operand byte for ops that take one (const/load/store/jmp/jz/jnz/call/ret/
/// syscall); it is ignored for argument-less ops.