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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user