From 1edd5efee2f80f0f88656ffe3ac2d8e16f392fd4 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sun, 19 Oct 2025 22:48:44 +0200 Subject: [PATCH] feat: Implement main consistency test script --- tests/consistency.sh | 291 ++++++++++++++++++++++++++++++++----------- 1 file changed, 215 insertions(+), 76 deletions(-) diff --git a/tests/consistency.sh b/tests/consistency.sh index 1fc9451..eb1ed47 100755 --- a/tests/consistency.sh +++ b/tests/consistency.sh @@ -1,36 +1,40 @@ #!/usr/bin/env bash -#  Copyright The Shufflecake Project Authors (2022) -#  Copyright The Shufflecake Project Contributors (2022) -#  Copyright Contributors to the The Shufflecake Project. +# Copyright The Shufflecake Project Authors (2022) +# Copyright The Shufflecake Project Contributors (2022) +# Copyright Contributors to the The Shufflecake Project. -#  See the AUTHORS file at the top-level directory of this distribution and at -#  +# See the AUTHORS file at the top-level directory of this distribution and at +# -#  This file is part of the program shufflecake-c, which is part of the Shufflecake  -#  Project. Shufflecake is a plausible deniability (hidden storage) layer for  -#  Linux. See . +# This file is part of the program shufflecake-c, which is part of the Shufflecake +# Project. Shufflecake is a plausible deniability (hidden storage) layer for +# Linux. See . -#  This program is free software: you can redistribute it and/or modify it  -#  under the terms of the GNU General Public License as published by the Free  -#  Software Foundation, either version 2 of the License, or (at your option)  -#  any later version. This program is distributed in the hope that it will be  -#  useful, but WITHOUT ANY WARRANTY; without even the implied warranty of  -#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General  -#  Public License for more details. You should have received a copy of the  -#  GNU General Public License along with this program.  -#  If not, see . +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 2 of the License, or (at your option) +# any later version. This program is distributed in the hope that it will be +# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program. +# If not, see . -# Benchmarking script for Shufflecake +# Consistency test script for Shufflecake # Variables SCRIPTNAME=$(basename "$0") SCRIPT_DIR="$(dirname "$(realpath "$0")")" LOOP_FILENAME="${SCRIPT_DIR}/sflc-test-loop-file.img" +HASHFILE1="${SCRIPT_DIR}/testfile1.sha256" +HASHFILE2="${SCRIPT_DIR}/testfile2.sha256" LOOP_DEVICE="" BLOCK_DEVICE="" -SFLCVOLUME="" -MNTPOINT="" +SFLCVOLUME1="" +MNTPOINT1="" +SFLCVOLUME2="" +MNTPOINT2="" TIMEFORMAT='%3R' SFLCPATH="" SFLCNAME="" @@ -44,7 +48,7 @@ NC='\033[0m' # No color # Help print_help() { -#       xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   79 chars +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars echo -e "${BLUE}Usage: ${SCRIPTNAME} [OPTION]... [BLOCKDEVICE]${NC}" echo " " echo "This script is used to test consistency of Shufflecake on this machine." @@ -61,20 +65,20 @@ print_help() { echo "5) Re-opens both devices, reads the files, and checks they didn't change." # TODO: add slice recovery check echo " " -#         xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   79 chars +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars echo "You must have already compiled and installed Shufflecake in order to run this." echo "The script will search for Shufflecake either in the default installation " echo "directory, or in the current directory, or in the parent directory (one level " echo -e "above this). If the module ${BLUE}dm-sflc${NC} is not loaded, the script " echo "will load it, execute, and then unload it." echo " " -#         xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   79 chars +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars echo "You can pass the path to a block device as an optional argument, otherwise the " echo "script will ask for one. If no path is provided, the script will create a 2 GiB" echo "local file and use it to back loop devices as a virtual block devices to be " echo "formatted with the appropriate tools. The file will be removed at the end." echo " " -#         xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   79 chars +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars echo -e "${BLUE}WARNING: ALL CONTENT OF THE PROVIDED BLOCK DEVICE WILL BE ERASED!${NC}" echo " " exit 0 @@ -99,20 +103,33 @@ usage() { cleanup() { echo "Exiting and cleaning..." - # Unmount filesystem if mounted - if [ -n "$MNTPOINT" ] && grep -qs "$MNTPOINT" /proc/mounts; then - echo "Unmounting \"$MNTPOINT\" ..." - umount "$MNTPOINT" + # Unmount filesystem 1 if mounted + if [ -n "$MNTPOINT1" ] && grep -qs "$MNTPOINT1" /proc/mounts; then + echo "Unmounting \"$MNTPOINT1\" ..." + umount "$MNTPOINT1" + fi + # Unmount filesystem 2 if mounted + if [ -n "$MNTPOINT2" ] && grep -qs "$MNTPOINT2" /proc/mounts; then + echo "Unmounting \"$MNTPOINT2\" ..." + umount "$MNTPOINT2" fi - # Remove mount point directory if it exists - if [ -n "$MNTPOINT" ] && [ -d "$MNTPOINT" ]; then - rmdir "$MNTPOINT" + # Remove mount point directory 1 if it exists + if [ -n "$MNTPOINT1" ] && [ -d "$MNTPOINT1" ]; then + rmdir "$MNTPOINT1" + fi + # Remove mount point directory 2 if it exists + if [ -n "$MNTPOINT2" ] && [ -d "$MNTPOINT2" ]; then + rmdir "$MNTPOINT2" fi - # Close shufflecake volume if open. Check if the SFLCVOLUME variable - # was set and if the corresponding device path still exists. - if [ -n "$SFLCVOLUME" ] && [ -b "$SFLCVOLUME" ]; then + # Close shufflecake volume if open. Check if *either* SFLCVOLUME variable + # was set and if *either* corresponding device path still exists. + local sflc_open=false + if [ -n "$SFLCVOLUME1" ] && [ -b "$SFLCVOLUME1" ]; then sflc_open=true; fi + if [ -n "$SFLCVOLUME2" ] && [ -b "$SFLCVOLUME2" ]; then sflc_open=true; fi + + if [ "$sflc_open" = true ]; then echo "Closing Shufflecake device on \"$BLOCK_DEVICE\" ..." "$SFLCNAME" close "$BLOCK_DEVICE" &> /dev/null fi @@ -128,6 +145,9 @@ cleanup() { rm -f "$LOOP_FILENAME" echo "Loop device detached and backing file deleted." fi + + # Clean up hash files + rm -f "$HASHFILE1" "$HASHFILE2" 2>/dev/null } # Set trap to run cleanup function on exit @@ -265,20 +285,6 @@ create_loop_device() { echo "$LOOP_DEVICE" } -# Finds the sflc volume that was created last -find_sflcvolname() { - local files=(/dev/mapper/sflc_*) - # Check if the glob expanded to any existing files - if [ ! -e "${files[0]}" ]; then - echo -e "${RED}ERROR: No sflc_ devices found in /dev/mapper !${NC}" >&2 - return 1 - fi - # Use printf to list one file per line, then sort and get the last one - printf '%s\n' "${files[@]}" | sort -t '_' -k 2n,2 -k 3n,3 | tail -n 1 - return 0 -} - - # Function for user confirmation confirm() { while true; do @@ -300,45 +306,178 @@ confirm() { # Tests do_test() { - TESTNAME="sflc-test" - RUNTIME="10" # running time in seconds - DATASIZE="100M" - TESTFILENAME="testfile" - echo "Starting benchmark for Shufflecake Lite..." + local TESTFILE1="testfile.100M" + local TESTFILE2="testfile.1400M" + # Use global HASHFILE1 and HASHFILE2 + MNTPOINT1=$(realpath "./sflc_mnt_1") # Use global + MNTPOINT2=$(realpath "./sflc_mnt_2") # Use global + + echo "Starting consistency test for Shufflecake Lite..." echo "Initializing block device \"$BLOCK_DEVICE\" with two Shufflecake Lite volumes (--skip-randfill)..." etime=$( (time echo -e "passwd1\npasswd2" | "$SFLCNAME" --skip-randfill -n 2 init "$BLOCK_DEVICE" &> /dev/null) 2>&1 ) if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Shufflecake init failed.${NC}" >&2; exit 1; fi echo -e "${GREEN}Action init took $etime seconds.${NC}" - echo "Shufflecake device initialized. Opening hidden volume (nr. 2)..." + # --- STAGE 1: OPEN, CREATE, HASH --- + echo -e "${BLUE}--- STAGE 1: Opening volumes, creating files, and calculating hashes ---${NC}" + + echo "Opening all volumes..." + local BEFORE_OPEN + BEFORE_OPEN=$(ls /dev/mapper/sflc_* 2>/dev/null | sort -V) + etime=$( (time echo -e "passwd2" | "$SFLCNAME" open "$BLOCK_DEVICE" &> /dev/null) 2>&1 ) if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Shufflecake open failed.${NC}" >&2; exit 1; fi + + local AFTER_OPEN + AFTER_OPEN=$(ls /dev/mapper/sflc_* 2>/dev/null | sort -V) + local NEW_VOLUMES + NEW_VOLUMES=$(comm -13 <(echo "$BEFORE_OPEN") <(echo "$AFTER_OPEN")) + + if [ $(echo "$NEW_VOLUMES" | wc -l) -ne 2 ]; then + echo -e "${RED}ERROR: Expected 2 new volumes to be created, but found $(echo "$NEW_VOLUMES" | wc -l).${NC}" >&2 + exit 1 + fi + + # Assign to global vars for cleanup. `sort -V` and `sed` ensure sflc_..._1 is vol1. + SFLCVOLUME1=$(echo "$NEW_VOLUMES" | sed -n '1p') + SFLCVOLUME2=$(echo "$NEW_VOLUMES" | sed -n '2p') + echo -e "${GREEN}Action open took $etime seconds.${NC}" + echo "Found volume 1: $SFLCVOLUME1" + echo "Found volume 2: $SFLCVOLUME2" - SFLCVOLUME=$(find_sflcvolname) - if [ $? -ne 0 ]; then exit 1; fi # Error message is already in the function - echo "Shufflecake Lite volume opened as \"$SFLCVOLUME\". Formatting with ext4..." - - mkfs.ext4 "$SFLCVOLUME" &> /dev/null - if [ $? -ne 0 ]; then echo -e "${RED}ERROR: mkfs.ext4 failed on \"$SFLCVOLUME\".${NC}" >&2; exit 1; fi + # --- Volume 1 --- + echo "Formatting $SFLCVOLUME1 with FAT32..." + mkfs.vfat "$SFLCVOLUME1" &> /dev/null + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: mkfs.vfat failed on \"$SFLCVOLUME1\".${NC}" >&2; exit 1; fi udevadm settle - echo "Volume \"$SFLCVOLUME\" formatted. Mounting that..." + + mkdir -p "$MNTPOINT1" + mount "$SFLCVOLUME1" "$MNTPOINT1" + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Mount failed for \"$SFLCVOLUME1\" on \"$MNTPOINT1\".${NC}" >&2; exit 1; fi + echo "Volume 1 mounted at $MNTPOINT1." - MNTPOINT=$(realpath "./sflc_mnt") - mkdir -p "$MNTPOINT" - if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Could not create mountpoint \"$MNTPOINT\".${NC}" >&2; exit 1; fi + echo "Creating 100MB test file on volume 1..." + dd if=/dev/urandom of="${MNTPOINT1}/${TESTFILE1}" bs=1M count=100 &> /dev/null + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: dd failed for volume 1.${NC}" >&2; exit 1; fi + + echo "Calculating SHA256 hash for 100MB file..." + sha256sum "${MNTPOINT1}/${TESTFILE1}" | cut -d' ' -f1 > "$HASHFILE1" + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: sha256sum failed for volume 1.${NC}" >&2; exit 1; fi + echo "Hash for volume 1 saved to $HASHFILE1." - mount "$SFLCVOLUME" "$MNTPOINT" - if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Mount failed for \"$SFLCVOLUME\" on \"$MNTPOINT\".${NC}" >&2; exit 1; fi - echo "Volume mounted at \"$MNTPOINT\". Starting fio tests..." + # --- Volume 2 --- + echo "Formatting $SFLCVOLUME2 with ext4..." + mkfs.ext4 -F "$SFLCVOLUME2" &> /dev/null # -F to force, non-interactive + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: mkfs.ext4 failed on \"$SFLCVOLUME2\".${NC}" >&2; exit 1; fi + udevadm settle - # TEST 01: random read - echo "Test 01: random read with a queue of 32 4kiB blocks on a file (${RUNTIME}s)..." - OUTPUT=$(fio --name="${TESTNAME}-r-rnd" --ioengine=libaio --iodepth=32 --rw=randread --bs=4k --numjobs=1 --direct=1 --size="$DATASIZE" --runtime="$RUNTIME" --time_based --end_fsync=1 --filename="${MNTPOINT}/${TESTFILENAME}" --output-format=json | jq '.jobs[] | {name: .jobname, read_iops: .read.iops, read_bw: .read.bw}') - if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Fio test 01 failed.${NC}" >&2; exit 1; fi - printf "${GREEN}%s${NC}\n" "$OUTPUT" + mkdir -p "$MNTPOINT2" + mount "$SFLCVOLUME2" "$MNTPOINT2" + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Mount failed for \"$SFLCVOLUME2\" on \"$MNTPOINT2\".${NC}" >&2; exit 1; fi + echo "Volume 2 mounted at $MNTPOINT2." - echo "Shufflecake Lite fio tests ended." + echo "Creating 1400MB test file on volume 2..." + dd if=/dev/urandom of="${MNTPOINT2}/${TESTFILE2}" bs=1M count=1400 &> /dev/null + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: dd failed for volume 2.${NC}" >&2; exit 1; fi + + echo "Calculating SHA256 hash for 1400MB file..." + sha256sum "${MNTPOINT2}/${TESTFILE2}" | cut -d' ' -f1 > "$HASHFILE2" + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: sha256sum failed for volume 2.${NC}" >&2; exit 1; fi + echo "Hash for volume 2 saved to $HASHFILE2." + + # --- STAGE 2: UNMOUNT, CLOSE --- + echo -e "${BLUE}--- STAGE 2: Unmounting and closing volumes ---${NC}" + + umount "$MNTPOINT1" + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Unmount failed for \"$MNTPOINT1\".${NC}" >&2; exit 1; fi + rmdir "$MNTPOINT1" + + umount "$MNTPOINT2" + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Unmount failed for \"$MNTPOINT2\".${NC}" >&2; exit 1; fi + rmdir "$MNTPOINT2" + + echo "Closing Shufflecake device on \"$BLOCK_DEVICE\" ..." + "$SFLCNAME" close "$BLOCK_DEVICE" &> /dev/null + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Shufflecake close failed.${NC}" >&2; exit 1; fi + + # Clear the globals so the cleanup trap doesn't try to re-close/re-umount + SFLCVOLUME1="" + SFLCVOLUME2="" + MNTPOINT1="" + MNTPOINT2="" + echo "Volumes unmounted and closed." + + # --- STAGE 3: RE-OPEN, RE-MOUNT, CHECK --- + echo -e "${BLUE}--- STAGE 3: Re-opening volumes and checking file integrity ---${NC}" + + echo "Re-opening all volumes..." + BEFORE_OPEN=$(ls /dev/mapper/sflc_* 2>/dev/null | sort -V) + + etime=$( (time echo -e "passwd2" | "$SFLCNAME" open "$BLOCK_DEVICE" &> /dev/null) 2>&1 ) + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Shufflecake (re-)open failed.${NC}" >&2; exit 1; fi + + AFTER_OPEN=$(ls /dev/mapper/sflc_* 2>/dev/null | sort -V) + NEW_VOLUMES=$(comm -13 <(echo "$BEFORE_OPEN") <(echo "$AFTER_OPEN")) + + if [ $(echo "$NEW_VOLUMES" | wc -l) -ne 2 ]; then + echo -e "${RED}ERROR: Expected 2 new volumes to be re-opened, but found $(echo "$NEW_VOLUMES" | wc -l).${NC}" >&2 + exit 1 + fi + + # Re-assign to global vars for cleanup + SFLCVOLUME1=$(echo "$NEW_VOLUMES" | sed -n '1p') + SFLCVOLUME2=$(echo "$NEW_VOLUMES" | sed -n '2p') + + echo -e "${GREEN}Action re-open (with passwd2) took $etime seconds.${NC}" + echo "Found volume 1: $SFLCVOLUME1" + echo "Found volume 2: $SFLCVOLUME2" + + # --- Volume 1 Check --- + MNTPOINT1=$(realpath "./sflc_mnt_1") # Re-set global for cleanup + mkdir -p "$MNTPOINT1" + mount "$SFLCVOLUME1" "$MNTPOINT1" + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Re-mount failed for \"$SFLCVOLUME1\" on \"$MNTPOINT1\".${NC}" >&2; exit 1; fi + echo "Volume 1 re-mounted at $MNTPOINT1." + + echo "Checking hash for 100MB file..." + local ORIGINAL_HASH1 + ORIGINAL_HASH1=$(cat "$HASHFILE1") + local NEW_HASH1 + NEW_HASH1=$(sha256sum "${MNTPOINT1}/${TESTFILE1}" | cut -d' ' -f1) + + if [ "$ORIGINAL_HASH1" == "$NEW_HASH1" ]; then + echo -e "${GREEN}SUCCESS: Volume 1 file hash matches!${NC}" + else + echo -e "${RED}FAILURE: Volume 1 file hash MISMATCH!${NC}" >&2 + echo "Original: $ORIGINAL_HASH1" >&2 + echo "New: $NEW_HASH1" >&2 + exit 1 + fi + + # --- Volume 2 Check --- + MNTPOINT2=$(realpath "./sflc_mnt_2") # Re-set global for cleanup + mkdir -p "$MNTPOINT2" + mount "$SFLCVOLUME2" "$MNTPOINT2" + if [ $? -ne 0 ]; then echo -e "${RED}ERROR: Re-mount failed for \"$SFLCVOLUME2\" on \"$MNTPOINT2\".${NC}" >&2; exit 1; fi + echo "Volume 2 re-mounted at $MNTPOINT2." + + echo "Checking hash for 1400MB file..." + local ORIGINAL_HASH2 + ORIGINAL_HASH2=$(cat "$HASHFILE2") + local NEW_HASH2 + NEW_HASH2=$(sha256sum "${MNTPOINT2}/${TESTFILE2}" | cut -d' ' -f1) + + if [ "$ORIGINAL_HASH2" == "$NEW_HASH2" ]; then + echo -e "${GREEN}SUCCESS: Volume 2 file hash matches!${NC}" + else + echo -e "${RED}FAILURE: Volume 2 file hash MISMATCH!${NC}" >&2 + echo "Original: $ORIGINAL_HASH2" >&2 + echo "New: $NEW_HASH2" >&2 + exit 1 + fi + + echo -e "${GREEN}--- ALL CONSISTENCY TESTS PASSED ---${NC}" # The cleanup trap will handle unmounting and closing. } @@ -350,9 +489,9 @@ do_test() { ##################################################################### # BANNER -#               xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   79 chars +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars echo -e "${BLUE}===============================================================================${NC}" -echo -e "${BLUE}                 Consistency Test for Shufflecake Lite${NC}" +echo -e "${BLUE} Consistency Test for Shufflecake Lite${NC}" echo -e "${BLUE}===============================================================================${NC}" @@ -390,7 +529,7 @@ echo " " # DETERMINE BLOCK DEVICE if [ -z "$BLOCK_DEVICE" ]; then echo "Now you will be asked to enter the path for a block device to be used for the " - echo "benchmarks (all content will be erased). If no path is provided (default"  + echo "benchmarks (all content will be erased). If no path is provided (default" echo "choice), then the script will create a 1 GiB file in the current directory and " echo "use it to back a loop device instead, then the file will be removed at the end." echo " "