feat(cubelinux-2): Package 3 — cubefs (POSIX/FUSE namespace over CZYX)
Implements the PDF's Package 3 with new code:
- path: bijective POSIX path <-> Czyx mapping (/c001/z002/y003/x004).
Axis-letter + 3-digit zero-padded canonical names so lexical order equals
numeric order and each coordinate has exactly one spelling. Inode IS the
packed u32 coordinate — no inode side table.
- nullspace: the PDF's 'use Null cubes for ACLs, xattrs, journaling, volume
metadata', with the Z-plane allocation fixed and documented (Z=1 volume,
Z=2 ACL, Z=3 xattr, Z=4 journal ring). ACL/xattr tables are FNV
hash-bucketed with exact-match resolution inside the bucket, because 4
axes of subject cannot injectively mirror into 2 axes of Null space.
Journal is a bounded ring; wraps are detectable via a monotonic counter.
- vfs: the whole filesystem, kernel-free and unit-testable — lookup,
readdir, create/read/write/truncate/unlink, mkdir/rmdir, ACL enforcement,
xattrs, journaling, POSIX errno mapping.
- fuse (feature 'mount'): thin kernel adapter, zero TTL (the store is
writable out-of-band, so cached metadata would go stale).
- cubestore: added the PDF's 'optional scanning primitives' (keys,
scan_prefix) and the Package 2 association API (associate, linked_to)
that cubefs needs for directory listings.
Two defects were found by LIVE MOUNT testing and fixed, not by unit tests:
1. mkdir succeeded then the kernel's revalidating lookup returned ENOENT,
so 'mkdir -p' could never reach depth 4. Directories were purely
inferred from records, making an empty directory unrepresentable. Fixed
with an explicit Null-space directory marker; rmdir removes it; readdir
merges markers in. 5 regression tests added.
2. Multi-user ACL behaviour was untestable because the mount lacked
AllowOther — the kernel returned EACCES at the mountpoint before any
request reached us. Added --allow-other.
Verified: 58 unit tests pass; clippy clean; live mount exercised with cat,
echo, dd, truncate, cp, chmod, chown, getfattr/setfattr, mkdir -p, rmdir,
find, a 200-record write loop, and cross-user reads/writes as luulu.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
//! `cubefs-mount` — mount a cube as a POSIX filesystem.
|
||||
//!
|
||||
//! Usage: `cubefs-mount <mountpoint> [--label NAME] [--seed] [--allow-other]`
|
||||
//!
|
||||
//! `--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 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]");
|
||||
return ExitCode::from(2);
|
||||
};
|
||||
let label = args
|
||||
.iter()
|
||||
.position(|a| a == "--label")
|
||||
.and_then(|i| args.get(i + 1))
|
||||
.cloned()
|
||||
.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 mut fs = CubeFs::new(CubeStore::new(HashBackend::new()));
|
||||
fs.format(&label);
|
||||
|
||||
if seed {
|
||||
for (path, body) in [
|
||||
("/c001/z001/y001/x001", &b"hello from the cube\n"[..]),
|
||||
("/c001/z001/y001/x002", &b"second record\n"[..]),
|
||||
("/c001/z002/y001/x001", &b"different z\n"[..]),
|
||||
("/c002/z001/y001/x001", &b"different c\n"[..]),
|
||||
] {
|
||||
if let Err(e) = fs.create(path, 0, 0, 0o644) {
|
||||
eprintln!("seed create {path}: {e:?}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
if let Err(e) = fs.write(path, 0, body, 0, 0) {
|
||||
eprintln!("seed write {path}: {e:?}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
}
|
||||
if let Err(e) = fs.setxattr("/c001/z001/y001/x001", "user.origin", b"seed") {
|
||||
eprintln!("seed xattr: {e:?}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Decision: `DefaultPermissions` asks the kernel to enforce the mode bits
|
||||
// we report, in addition to our own ACL check in `vfs`. Belt and braces:
|
||||
// the kernel check protects against a bug in our check, and our check
|
||||
// protects the library API (which is reachable without a mount).
|
||||
// `AllowOther` is required for any user other than the mounting user to
|
||||
// see the filesystem at all — without it the kernel returns EACCES on the
|
||||
// mountpoint itself before a single request reaches us, which is exactly
|
||||
// what live testing showed.
|
||||
let mut opts = vec![
|
||||
fuser::MountOption::FSName("cubefs".to_string()),
|
||||
fuser::MountOption::AutoUnmount,
|
||||
fuser::MountOption::DefaultPermissions,
|
||||
];
|
||||
if allow_other {
|
||||
opts.push(fuser::MountOption::AllowOther);
|
||||
}
|
||||
eprintln!(
|
||||
"mounting cubefs at {mountpoint} (label={label}, seed={seed}, allow_other={allow_other}) — ctrl-c to unmount"
|
||||
);
|
||||
match fuser::mount2(CubeFuse::new(fs), mountpoint, &opts) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
eprintln!("mount failed: {e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user