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
+41 -13
View File
@@ -1,25 +1,31 @@
//! `cubefs-mount` — mount a cube as a POSIX filesystem.
//!
//! Usage: `cubefs-mount <mountpoint> [--label NAME] [--seed] [--allow-other]`
//! Usage:
//! cubefs-mount <mountpoint> [--label NAME] [--seed] [--allow-other]
//! [--socket PATH]
//!
//! `--socket PATH` mounts the **daemon's** durable store over a Unix socket
//! (see `cubefs::DaemonBackend`). A write through the FUSE mount then lands in
//! the daemon's WAL and survives an unmount — or a daemon restart — exactly as
//! the PDF's "cubefs is a FUSE view of the cube" intends. Without `--socket`
//! the mount uses the in-memory `HashBackend` (ephemeral; every write is lost
//! on unmount), which is still useful for namespace-mapping tests and for
//! machines with no `cube-server` running.
//!
//! `--allow-other` lets users other than the mounting user reach the
//! filesystem. Without it the kernel rejects them at the mountpoint before any
//! request reaches us, so multi-user ACL behaviour cannot be observed.
//!
//! The backing store is the in-memory [`HashBackend`] for now: Package 3's job
//! is the *namespace mapping*, and a durable on-disk backend is a cubestore
//! concern that gets swapped in by changing one type parameter here. `--seed`
//! populates a few records so the mount has something to `ls`.
use cubefs::fuse::CubeFuse;
use cubefs::CubeFs;
use cubestore::{CubeStore, HashBackend};
use cubefs::{CubeFs, DaemonBackend};
use cubestore::{CubeBackend, CubeStore, HashBackend};
use std::process::ExitCode;
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().collect();
let Some(mountpoint) = args.get(1).filter(|a| !a.starts_with("--")) else {
eprintln!("usage: cubefs-mount <mountpoint> [--label NAME] [--seed]");
eprintln!(
"usage: cubefs-mount <mountpoint> [--label NAME] [--seed] [--socket PATH] [--allow-other]"
);
return ExitCode::from(2);
};
let label = args
@@ -30,8 +36,29 @@ fn main() -> ExitCode {
.unwrap_or_else(|| "cube0".to_string());
let seed = args.iter().any(|a| a == "--seed");
let allow_other = args.iter().any(|a| a == "--allow-other");
let socket = args
.iter()
.position(|a| a == "--socket")
.and_then(|i| args.get(i + 1).cloned());
let mut fs = CubeFs::new(CubeStore::new(HashBackend::new()));
// Backing store: the daemon's durable store (over a socket) when `--socket`
// is given, otherwise an ephemeral in-memory map. Both are type-erased to
// `Box<dyn CubeBackend + Send + Sync>` so `CubeFs` has a single concrete
// type regardless of which backend was chosen at runtime.
let mut fs: CubeFs<Box<dyn CubeBackend + Send + Sync>> = match &socket {
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>,
))
}
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>,
))
}
};
fs.format(&label);
if seed {
@@ -73,9 +100,10 @@ fn main() -> ExitCode {
opts.push(fuser::MountOption::AllowOther);
}
eprintln!(
"mounting cubefs at {mountpoint} (label={label}, seed={seed}, allow_other={allow_other}) — ctrl-c to unmount"
"mounting cubefs at {mountpoint} (label={label}, socket={:?}, seed={seed}, allow_other={allow_other}) — ctrl-c to unmount",
socket
);
match fuser::mount2(CubeFuse::new(fs), mountpoint, &opts) {
match fuser::mount2(cubefs::fuse::CubeFuse::new(fs), mountpoint, &opts) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("mount failed: {e}");