From ab404093162320f80f9f895f6bff27397034d302 Mon Sep 17 00:00:00 2001 From: hermes Date: Thu, 13 Aug 2026 10:07:43 -0400 Subject: [PATCH] cubefs: add .czyx.C.Z.Y.X FUSE magic-prefix for Phase-3 'open by CZYX' (same inode as canonical path) --- STARTUP-README.md | 17 +++++++++---- cubefs/src/fuse.rs | 19 +++++++++++++++ cubefs/src/path.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/STARTUP-README.md b/STARTUP-README.md index 00cc3b7..677ab14 100644 --- a/STARTUP-README.md +++ b/STARTUP-README.md @@ -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 - ` 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 ` 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 + diff --git a/cubefs/src/fuse.rs b/cubefs/src/fuse.rs index a9d25bd..dc17adc 100644 --- a/cubefs/src/fuse.rs +++ b/cubefs/src/fuse.rs @@ -143,6 +143,25 @@ impl CubeFuse { impl Filesystem for CubeFuse { 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 = 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)) diff --git a/cubefs/src/path.rs b/cubefs/src/path.rs index cbb6d77..c49a07c 100644 --- a/cubefs/src/path.rs +++ b/cubefs/src/path.rs @@ -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.