feat: Implement main consistency test script

This commit is contained in:
Tommaso Gagliardoni 2025-10-19 22:48:44 +02:00
parent 35522bc595
commit 1edd5efee2

View file

@ -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
#  <https://www.shufflecake.net/permalinks/shufflecake-c/AUTHORS>
# See the AUTHORS file at the top-level directory of this distribution and at
# <https://www.shufflecake.net/permalinks/shufflecake-c/AUTHORS>
#  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 <https://www.shufflecake.net>.
# 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 <https://www.shufflecake.net>.
#  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 <https://www.gnu.org/licenses/>.
# 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 <https://www.gnu.org/licenses/>.
# 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 " "