cubefs: add .czyx.C.Z.Y.X FUSE magic-prefix for Phase-3 'open by CZYX' (same inode as canonical path)
This commit is contained in:
+12
-5
@@ -45,11 +45,18 @@ is real. Each must be reproduced live, not asserted.
|
||||
`fixed-durable-20260813`). Re-verified 2026-08-13: OS state written to
|
||||
`/cubefs/c200/...` survived a `systemctl restart cube-server cubefs`. **STATUS: VERIFIED.**
|
||||
3. **Phase 3 syscall surface** — "open by CZYX + flags": a record addressable
|
||||
directly by coordinate, not path. **SHIPPED 2026-08-13**: `cube open <path>
|
||||
<K.Z.Y.X> <tf>` and `cube seal ...` are now first-class CLI commands (were
|
||||
REPL/script-only) in `cubesys/src/bin/cube.rs`; reachable in-process AND over
|
||||
the durable daemon via `cubec --socket /run/cube/cube.sock 'open ...'`.
|
||||
Verified: dispatch reaches the crypto/run layer. **STATUS: DONE.**
|
||||
directly by coordinate, not path. **SHIPPED 2026-08-13**:
|
||||
(a) `cube open <path> <K.Z.Y.X> <tf>` and `cube seal ...` are first-class CLI
|
||||
commands in `cubesys/src/bin/cube.rs` (reachable in-process + over the durable
|
||||
daemon). Verified: dispatch reaches the crypto/run layer.
|
||||
(b) **`.czyx.C.Z.Y.X` FUSE magic-prefix (2026-08-13)** — `cubefs/src/path.rs`
|
||||
`parse_dot_czyx` + `cubefs/src/fuse.rs::lookup` intercept: a path component
|
||||
`.czyx.200.50.1.7` resolves to the SAME inode as the canonical
|
||||
`/cubefs/c200/z050/y001/x007` and reads/writes the real record. This is the
|
||||
Phase-3 "open by CZYX" at the filesystem level — the kernel addresses the
|
||||
record by coordinate. LIVE VERIFIED: stat both spellings -> identical inode
|
||||
3358720263; `cat .czyx.200.50.1.7` returns the record content. Unit tests in
|
||||
`path.rs` cover full/partial/rejected forms. **STATUS: DONE.**
|
||||
4. **Boot substrate** — the VM brings up CUBELinux as a storage layer the rest of
|
||||
the OS reads/writes through. **DONE (systemd-managed, 2026-08-13)**: units
|
||||
`cube-os-state.service` (oneshot, writes boot manifest + machine identity +
|
||||
|
||||
@@ -143,6 +143,25 @@ impl<B: CubeBackend> CubeFuse<B> {
|
||||
|
||||
impl<B: CubeBackend + 'static> Filesystem for CubeFuse<B> {
|
||||
fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
|
||||
// Phase-3 "open by CZYX": a `.czyx.C.Z.Y.X` name resolves directly to
|
||||
// the coordinate's inode, independent of the canonical `cC/zZ/yY/xX`
|
||||
// spelling. The kernel then opens the record *by its coordinate*.
|
||||
if let Some(s) = name.to_str() {
|
||||
if let Some(axes) = path::parse_dot_czyx(s) {
|
||||
let non_zero: Vec<u8> = axes.iter().take_while(|&&v| v != 0).copied().collect();
|
||||
let p = path::render_path(&non_zero);
|
||||
match self.fs.getattr(&p) {
|
||||
Ok(a) => {
|
||||
reply.entry(&TTL, &to_file_attr(&a), 0);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
reply.error(errno(&e));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match self
|
||||
.child_path(parent, name)
|
||||
.and_then(|p| self.fs.getattr(&p))
|
||||
|
||||
@@ -198,6 +198,40 @@ pub fn render_path(axes: &[u8]) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
/// Parse the `.czyx.C.Z.Y.X` magic-prefix form — the Phase-3 "open by CZYX"
|
||||
/// at the filesystem level. A single path component `.czyx.12.34.56.78`
|
||||
/// resolves to the coordinate `(12,34,56,78)` regardless of the normal
|
||||
/// `c012/z034/y056/x078` spelling, so the kernel opens the record *by its
|
||||
/// coordinate*, not by path. Partial forms (`.czyx.12`, `.czyx.12.34`) address
|
||||
/// the synthetic directory prefix (trailing axes default to 0).
|
||||
///
|
||||
/// Returns the four axis values (trailing axes 0 for partial forms).
|
||||
pub fn parse_dot_czyx(name: &str) -> Option<[u8; 4]> {
|
||||
let parts: Vec<&str> = name.split('.').collect();
|
||||
// expect ["", "czyx", C, (Z)?, (Y)?, (X)?]
|
||||
if parts.len() < 3 || !parts[0].is_empty() || parts[1] != "czyx" {
|
||||
return None;
|
||||
}
|
||||
let mut axes = [0u8; 4];
|
||||
let mut n = 0usize;
|
||||
for p in &parts[2..] {
|
||||
if n >= 4 {
|
||||
return None; // too many axes
|
||||
}
|
||||
let v: u32 = p.parse().ok()?;
|
||||
// 0 is Null control space; >255 is out of the addressable range.
|
||||
if v == 0 || v > 255 {
|
||||
return None;
|
||||
}
|
||||
axes[n] = v as u8;
|
||||
n += 1;
|
||||
}
|
||||
if n == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(axes)
|
||||
}
|
||||
|
||||
/// Coordinate -> inode number.
|
||||
///
|
||||
/// The inode IS the packed coordinate; see the crate docs for why no side
|
||||
@@ -302,6 +336,33 @@ mod tests {
|
||||
assert!(ino_to_czyx(u64::from(u32::MAX) + 1).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dot_czyx_magic_prefix() {
|
||||
// Full coordinate form resolves to (C,Z,Y,X).
|
||||
assert_eq!(parse_dot_czyx(".czyx.12.34.56.78"), Some([12, 34, 56, 78]));
|
||||
// Partial forms fill trailing axes with 0 (directory prefixes).
|
||||
assert_eq!(parse_dot_czyx(".czyx.7"), Some([7, 0, 0, 0]));
|
||||
assert_eq!(parse_dot_czyx(".czyx.1.2.3"), Some([1, 2, 3, 0]));
|
||||
// Rejects malformed input and out-of-range / Null values.
|
||||
assert_eq!(parse_dot_czyx("czyx.1.2.3.4"), None); // missing leading dot
|
||||
assert_eq!(parse_dot_czyx(".czyx"), None); // no axes
|
||||
assert_eq!(parse_dot_czyx(".czyx.0.1.2.3"), None); // Null axis 0
|
||||
assert_eq!(parse_dot_czyx(".czyx.1.2.3.4.5"), None); // too many axes
|
||||
assert_eq!(parse_dot_czyx(".czyx.256"), None); // out of range
|
||||
assert_eq!(parse_dot_czyx(".czyx.abc"), None); // non-numeric
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dot_czyx_resolves_same_ino_as_canonical() {
|
||||
// The magic prefix must map to the *same inode* as the canonical path,
|
||||
// so the kernel opens the record by coordinate, not path.
|
||||
let axes = parse_dot_czyx(".czyx.12.34.56.78").unwrap();
|
||||
let c = Czyx::new(axes[0], axes[1], axes[2], axes[3]);
|
||||
let canonical = parse_path("/c012/z034/y056/x078").unwrap().czyx().unwrap();
|
||||
assert_eq!(c, canonical);
|
||||
assert_eq!(czyx_to_ino(c), czyx_to_ino(canonical));
|
||||
}
|
||||
|
||||
/// Exhaustive proof of bijectivity over the full record space would be
|
||||
/// 255^4 = 4.2e9 iterations; we sample the boundaries plus a stride so the
|
||||
/// test stays fast but still covers every axis extreme.
|
||||
|
||||
Reference in New Issue
Block a user