build: ./check — canonical verification gate for CUBELinux-2
The workspace had no single verification entrypoint, so every claim of 'green' rested on an ad-hoc command chosen after the fact. ./check makes it one command, mirroring the convention already used by the original build. ./check fmt + tests + clippy -D warnings ./check quick tests only ./check mount the above + live FUSE end-to-end (root, 27 assertions) The FUSE adapter compiles in every stage (--features cubefs/mount) so the kernel-facing code can never silently rot behind a feature flag. The live mount stage is opt-in because it needs root, /dev/fuse and attr — but it is the only stage that exercises the real kernel VFS path, and it encodes the two Package 3 regressions (mkdir -p to depth 4, cross-user ACLs) that every unit test missed. Adopting clippy -D warnings immediately paid for itself: it failed on two pre-existing Package 1 defects that per-crate greps had let through — a needless_range_loop in TriEnc::unpack_6 and a doc comment orphaned by a blank line in HeaderFlags. Both fixed. Verified: ./check green; ./check mount green (27/27, no leftover mounts).
This commit is contained in:
@@ -0,0 +1,126 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Canonical verification for the CUBELinux-2 workspace.
|
||||||
|
#
|
||||||
|
# ./check full gate (fmt, tests, clippy -D warnings)
|
||||||
|
# ./check quick tests only
|
||||||
|
# ./check mount full gate + live FUSE mount end-to-end (needs root)
|
||||||
|
#
|
||||||
|
# Exit 0 means the tree on disk is green. This is the single source of truth;
|
||||||
|
# do not claim verification from an ad-hoc run.
|
||||||
|
#
|
||||||
|
# Why `mount` is opt-in: it needs root, /dev/fuse, and the `attr` package, and
|
||||||
|
# it is the only stage that exercises the real kernel VFS path. Two Package 3
|
||||||
|
# defects (mkdir -p to depth 4, cross-user ACLs) passed every unit test and
|
||||||
|
# were caught only here — so run it before calling filesystem work done.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
command -v cargo >/dev/null || PATH="$HOME/.cargo/bin:$PATH"
|
||||||
|
command -v cargo >/dev/null || { echo "cargo not found; add ~/.cargo/bin to PATH" >&2; exit 127; }
|
||||||
|
|
||||||
|
FEAT="--features cubefs/mount" # the FUSE adapter must compile in every gate
|
||||||
|
JOBS="${JOBS:-4}" # never saturate all 8 cores by default
|
||||||
|
|
||||||
|
step() { printf '\n\033[1m### %s\033[0m\n' "$1"; }
|
||||||
|
|
||||||
|
if [ "${1:-}" = quick ]; then
|
||||||
|
step "tests"
|
||||||
|
exec cargo test --workspace $FEAT --jobs "$JOBS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
step "1/3 format"
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
|
||||||
|
step "2/3 tests"
|
||||||
|
cargo test --workspace $FEAT --jobs "$JOBS"
|
||||||
|
|
||||||
|
step "3/3 clippy"
|
||||||
|
cargo clippy --workspace --all-targets $FEAT --jobs "$JOBS" -- -D warnings
|
||||||
|
|
||||||
|
[ "${1:-}" = mount ] || { printf '\n\033[32mALL CHECKS PASSED\033[0m (run ./check mount for live FUSE e2e)\n'; exit 0; }
|
||||||
|
|
||||||
|
step "4/4 live FUSE mount"
|
||||||
|
[ "$(id -u)" -eq 0 ] || { echo "SKIP: live mount needs root" >&2; exit 1; }
|
||||||
|
command -v getfattr >/dev/null || { echo "missing getfattr: apt-get install attr" >&2; exit 1; }
|
||||||
|
|
||||||
|
cargo build $FEAT --jobs "$JOBS"
|
||||||
|
BIN=$PWD/target/debug/cubefs-mount
|
||||||
|
MNT=$(mktemp -d /tmp/cubefs-check.XXXXXX)
|
||||||
|
PASS=0; FAIL=0
|
||||||
|
cleanup() { fusermount3 -u "$MNT" 2>/dev/null || true; sleep 0.5; rmdir "$MNT" 2>/dev/null || true; }
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
ok() { printf 'PASS %s\n' "$1"; PASS=$((PASS+1)); }
|
||||||
|
no() { printf '\033[31mFAIL %s -- %s\033[0m\n' "$1" "${2-}"; FAIL=$((FAIL+1)); }
|
||||||
|
is() { [ "$2" = "$3" ] && ok "$1" || no "$1" "want[$3] got[$2]"; }
|
||||||
|
# A command that must fail (permission, ENOTEMPTY, EINVAL...).
|
||||||
|
nay() { local m=$1; shift; "$@" >/dev/null 2>&1 && no "$m" "unexpectedly succeeded" || ok "$m"; }
|
||||||
|
|
||||||
|
set +e
|
||||||
|
"$BIN" "$MNT" --seed --label check --allow-other >/dev/null 2>&1 &
|
||||||
|
sleep 2
|
||||||
|
mount | grep -q "on $MNT type fuse" && ok mounted || { no mounted; exit 1; }
|
||||||
|
|
||||||
|
F=$MNT/c001/z001/y001/x001
|
||||||
|
is "seed read" "$(cat $F)" "hello from the cube"
|
||||||
|
is "root listing" "$(ls $MNT | tr '\n' ' ')" "c001 c002 "
|
||||||
|
is "stat size" "$(stat -c %s $F)" "20"
|
||||||
|
is "partial read" "$(dd if=$F bs=1 skip=6 count=4 2>/dev/null)" "from"
|
||||||
|
|
||||||
|
echo posix-write > $MNT/c001/z001/y001/x003
|
||||||
|
is "write+read" "$(cat $MNT/c001/z001/y001/x003)" "posix-write"
|
||||||
|
truncate -s 5 $MNT/c001/z001/y001/x003
|
||||||
|
is "truncate" "$(cat $MNT/c001/z001/y001/x003)" "posix"
|
||||||
|
printf XY >> $MNT/c001/z001/y001/x003
|
||||||
|
is "append" "$(cat $MNT/c001/z001/y001/x003)" "posixXY"
|
||||||
|
rm $MNT/c001/z001/y001/x003
|
||||||
|
is "unlink" "$(ls $MNT/c001/z001/y001 | tr '\n' ' ')" "x001 x002 "
|
||||||
|
|
||||||
|
# Regression: empty directories must be representable, or mkdir -p can never
|
||||||
|
# reach depth 4 (the kernel's revalidating lookup returned ENOENT).
|
||||||
|
mkdir -p $MNT/c050/z001/y001 && ok "mkdir -p depth3" || no "mkdir -p depth3"
|
||||||
|
is "empty dir listed" "$(ls $MNT | grep -c c050)" "1"
|
||||||
|
is "empty dir empty" "$(ls $MNT/c050/z001/y001 | wc -l)" "0"
|
||||||
|
echo deep > $MNT/c050/z001/y001/x001
|
||||||
|
is "create under new" "$(cat $MNT/c050/z001/y001/x001)" "deep"
|
||||||
|
nay "rmdir nonempty refused" rmdir $MNT/c050
|
||||||
|
mkdir $MNT/c060 && rmdir $MNT/c060
|
||||||
|
is "mkdir/rmdir empty" "$(ls $MNT | grep -c c060)" "0"
|
||||||
|
|
||||||
|
mkdir -p $MNT/c255/z255/y255 && echo edge > $MNT/c255/z255/y255/x255
|
||||||
|
is "axis-255 corner" "$(cat $MNT/c255/z255/y255/x255)" "edge"
|
||||||
|
nay "reject non-canonical x1" tee $MNT/c001/z001/y001/x1 <<<x
|
||||||
|
|
||||||
|
cp /etc/hostname $MNT/c050/z001/y001/x002
|
||||||
|
diff -q /etc/hostname $MNT/c050/z001/y001/x002 >/dev/null && ok "cp byte-identical" || no "cp byte-identical"
|
||||||
|
|
||||||
|
setfattr -n user.a -v 1 $F; setfattr -n user.b -v 2 $F; setfattr -n user.d -v D $MNT/c001
|
||||||
|
is "xattr count file" "$(getfattr -d $F 2>/dev/null | grep -c ^user)" "3"
|
||||||
|
is "xattr on dir" "$(getfattr -n user.d --only-values $MNT/c001 2>/dev/null)" "D"
|
||||||
|
setfattr -x user.a $F
|
||||||
|
is "xattr removed" "$(getfattr -d $F 2>/dev/null | grep -c '^user\.a')" "0"
|
||||||
|
|
||||||
|
# Regression: cross-user ACLs need --allow-other, else the kernel returns
|
||||||
|
# EACCES at the mountpoint before any request reaches us.
|
||||||
|
if id luulu >/dev/null 2>&1; then
|
||||||
|
chown 0:0 $F; chmod 644 $F
|
||||||
|
is "644 root, other reads" "$(sudo -u luulu cat $F)" "hello from the cube"
|
||||||
|
chmod 600 $F
|
||||||
|
nay "600 root denies other" sudo -u luulu cat $F
|
||||||
|
chown 1000:1000 $F; chmod 600 $F
|
||||||
|
is "600 owned, owner reads" "$(sudo -u luulu cat $F)" "hello from the cube"
|
||||||
|
sudo -u luulu tee $F >/dev/null <<<by-other
|
||||||
|
is "other writes" "$(cat $F)" "by-other"
|
||||||
|
else
|
||||||
|
echo "SKIP: user luulu absent, cross-user ACL stage not run"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p $MNT/c100/z001/y001
|
||||||
|
for i in $(seq 1 200); do printf 'rec%03d\n' $i > $MNT/c100/z001/y001/x$(printf %03d $i); done
|
||||||
|
is "200 records" "$(ls $MNT/c100/z001/y001 | wc -l)" "200"
|
||||||
|
is "record 137" "$(cat $MNT/c100/z001/y001/x137)" "rec137"
|
||||||
|
set -e
|
||||||
|
|
||||||
|
printf '\n%d passed, %d failed\n' "$PASS" "$FAIL"
|
||||||
|
[ "$FAIL" -eq 0 ] || exit 1
|
||||||
|
printf '\n\033[32mALL CHECKS PASSED\033[0m\n'
|
||||||
@@ -155,9 +155,8 @@ impl TriEnc {
|
|||||||
let v = word.0;
|
let v = word.0;
|
||||||
let control = ((v >> 60) & 0x0F) as u8;
|
let control = ((v >> 60) & 0x0F) as u8;
|
||||||
let mut ascii = [0u8; 6];
|
let mut ascii = [0u8; 6];
|
||||||
for i in 0..6 {
|
for (i, b) in ascii.iter_mut().enumerate() {
|
||||||
let shift = 8 * (5 - i);
|
*b = ((v >> (8 * (5 - i))) & 0xFF) as u8;
|
||||||
ascii[i] = ((v >> shift) & 0xFF) as u8;
|
|
||||||
}
|
}
|
||||||
(control, ascii)
|
(control, ascii)
|
||||||
}
|
}
|
||||||
@@ -186,8 +185,8 @@ impl HeaderFlags {
|
|||||||
pub const PERM_REMOTE_USER: u16 = 1 << 6;
|
pub const PERM_REMOTE_USER: u16 = 1 << 6;
|
||||||
/// Flag 8: has outgoing association links.
|
/// Flag 8: has outgoing association links.
|
||||||
pub const HAS_ASSOCIATIONS: u16 = 1 << 7;
|
pub const HAS_ASSOCIATIONS: u16 = 1 << 7;
|
||||||
/// Flag 255 (conceptual end-of-header) is represented out-of-band by the
|
// Flag 255 (conceptual end-of-header) is represented out-of-band by the
|
||||||
/// record serializer; there is no bit for it.
|
// record serializer; there is no bit for it.
|
||||||
|
|
||||||
/// Construct from a raw bitmask.
|
/// Construct from a raw bitmask.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
Reference in New Issue
Block a user