# Test-only helper for app/test/disksVmIntegration.test.js (never shipped as an app operation). # Creates and removes dedicated loop-backed disk images under /var/tmp/chj-disks-test and prepares LVM/LUKS layers # on them. Every operation first proves that the device belongs to an image created by this helper; otherwise it # stops. Started by the test through PrivilegedExecutor as "sh -c SCRIPT chj OP ARGS...". set -u LC_ALL=C; export LC_ALL PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH DIR=/var/tmp/chj-disks-test printf '\036CHJ_READY\n' op=${1:-}; [ $# -gt 0 ] && shift die() { printf '\036CHJ_ERR=%s\n' "$1"; printf '%s\n' "${2:-}" >&2; exit 3; } valid_id() { case $1 in ''|*[!a-z0-9]*) return 1 ;; esac; [ ${#1} -le 24 ]; } loop_of() { case $1 in /dev/loop[0-9]*) printf '%s\n' "$1" | sed 's#^/dev/\(loop[0-9]*\).*#\1#' ;; *) return 1 ;; esac; } # The device (loop disk or one of its partitions) must be backed by $DIR/chjtest-.img. test_device() { d=$(loop_of "$1") || die NOT_TEST_DEVICE "$1 is not a loop device" b=$(cat "/sys/block/$d/loop/backing_file" 2>/dev/null) case $b in "$DIR"/chjtest-[a-z0-9]*.img) ;; *) die NOT_TEST_DEVICE "$1 is not backed by a CH-J test image" ;; esac [ -b "$1" ] || die NOT_TEST_DEVICE "$1 is not a block device" } test_mount() { case $1 in /mnt/chj-disks-test-[a-z0-9]*) ;; *) die NOT_TEST_MOUNT "$1 is not a test mount point" ;; esac; case $1 in *..*|*[!A-Za-z0-9/_-]*) die NOT_TEST_MOUNT bad ;; esac; } case $op in create) # ID MIB valid_id "$1" || die ARGS id; case $2 in ''|*[!0-9]*) die ARGS size ;; esac mkdir -p "$DIR" && chmod 700 "$DIR" img=$DIR/chjtest-$1.img; [ -e "$img" ] && die EXISTS "$img exists" truncate -s "${2}M" "$img" || die FAILED truncate # util-linux has --show; BusyBox losetup (Alpine) needs the free device first. dev=$(losetup -f --show -P "$img" 2>/dev/null) || { dev=$(losetup -f) && losetup -P "$dev" "$img"; } || { rm -f "$img"; die FAILED losetup; } printf 'dev=%s\nimg=%s\n' "$dev" "$img" ;; grow) # DEV MIB test_device "$1"; case $2 in ''|*[!0-9]*) die ARGS size ;; esac truncate -s "${2}M" "$(cat "/sys/block/$(loop_of "$1")/loop/backing_file")" && losetup -c "$1" || die FAILED grow printf 'size=%s\n' "$(blockdev --getsize64 "$1")" ;; write) # MOUNTPOINT: random data + checksums test_mount "$1" dd if=/dev/urandom of="$1/data.bin" bs=1024 count=16384 2>/dev/null && cp /etc/os-release "$1/os-release" && sync || die FAILED write (cd "$1" && sha256sum data.bin os-release) ;; sums) test_mount "$1"; (cd "$1" && sha256sum data.bin os-release) ;; tamper) # DEV: change the partition table behind a reviewed plan (adds a 1 MiB partition at the end) test_device "$1" printf ',1M\n' | sfdisk --append --no-reread "$1" >/dev/null 2>&1 || die FAILED tamper partx -u "$1" 2>/dev/null; echo ok ;; lvm) # PARTITION VG: PV + VG + 200 MiB LV with ext4 test_device "$1"; case $2 in chjtest[a-z0-9]*) ;; *) die ARGS vg ;; esac pvcreate -ff -y "$1" >/dev/null && vgcreate "$2" "$1" >/dev/null && lvcreate -y -L 200M -n data "$2" >/dev/null && mkfs.ext4 -q "/dev/$2/data" || die FAILED lvm echo "lv=/dev/$2/data" ;; luks) # PARTITION NAME keyring|dm-crypt: LUKS2 with a throw-away random key file (never printed, deleted after open) test_device "$1"; case $2 in chjtest[a-z0-9]*) ;; *) die ARGS name ;; esac key=/run/$2.key; umask 077; head -c 64 /dev/urandom > "$key" cryptsetup luksFormat --batch-mode --type luks2 --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --key-file "$key" "$1" >/dev/null 2>&1 || { rm -f "$key"; die FAILED luksFormat; } if [ "$3" = dm-crypt ]; then cryptsetup open --disable-keyring --key-file "$key" "$1" "$2"; else cryptsetup open --key-file "$key" "$1" "$2"; fi; rc=$? rm -f "$key"; [ $rc = 0 ] || die FAILED open mkfs.ext4 -q "/dev/mapper/$2" || die FAILED mkfs printf 'map=/dev/mapper/%s\nkeylocation=%s\n' "$2" "$(cryptsetup status "$2" | awk -F': *' '/key location/ { print $2 }')" ;; hold-lock) # ID: simulate a live disk job holding the server-wide lock (a sleeping process with a runner command line) valid_id "$1" || die ARGS id; [ -e /run/chj-disks.lock ] && die BUSY "lock already held" jobs=/var/lib/chj-server-manager/disks/jobs; job=chjtest-$1-holder; mkdir -p "$jobs/$job" # Two commands keep bash from exec-ing sleep, so the process keeps the runner-like command line. setsid sh -c 'sleep 120; :' job-run "$job" /dev/null 2>&1 & pid=$! printf '%s\n' "$pid" > "$jobs/$job/pid"; echo running > "$jobs/$job/state"; echo 1 > "$jobs/$job/total" mkdir /run/chj-disks.lock && printf '%s\n' "$job" > /run/chj-disks.lock/job echo "pid=$pid" ;; release-lock) # ID valid_id "$1" || die ARGS id; job=chjtest-$1-holder; jobs=/var/lib/chj-server-manager/disks/jobs [ -r "$jobs/$job/pid" ] && kill "$(cat "$jobs/$job/pid")" 2>/dev/null [ "$(cat /run/chj-disks.lock/job 2>/dev/null)" = "$job" ] && rm -rf /run/chj-disks.lock rm -rf -- "${jobs:?}/$job"; echo released ;; cleanup) # ID: only resources of this test run valid_id "$1" || die ARGS id for m in $(awk '$2 ~ /^\/mnt\/chj-disks-test-/ { print $2 }' /proc/mounts | sort -r); do umount "$m" 2>/dev/null; rmdir "$m" 2>/dev/null; done for map in /dev/mapper/chjtest*; do [ -e "$map" ] || continue; case ${map##*/} in *-*) continue ;; esac; cryptsetup close "${map##*/}" 2>/dev/null; done for img in "$DIR"/chjtest-"$1"*.img; do [ -e "$img" ] || continue # sysfs instead of "losetup -j" (not available in BusyBox). for sys in /sys/block/loop*; do [ "$(cat "$sys/loop/backing_file" 2>/dev/null)" = "$img" ] || continue dev=/dev/${sys##*/} for vg in $(pvs --noheadings -o vg_name "$dev"p* 2>/dev/null | tr -d ' ' | sort -u); do case $vg in chjtest*) vgremove -ff -y "$vg" >/dev/null 2>&1 ;; esac; done for p in "$dev"p*; do [ -b "$p" ] && pvremove -ff -y "$p" >/dev/null 2>&1; done for map in /dev/mapper/chjtest*; do [ -e "$map" ] || continue; cryptsetup close "${map##*/}" 2>/dev/null; done losetup -d "$dev" 2>/dev/null done rm -f -- "$img" done for m in /mnt/chj-disks-test-*; do [ -d "$m" ] && rmdir "$m" 2>/dev/null; done [ "$(cat /run/chj-disks.lock/job 2>/dev/null)" = "chjtest-$1-holder" ] && rm -rf /run/chj-disks.lock j=/var/lib/chj-server-manager/disks/jobs/chjtest-$1-holder; [ -r "$j/pid" ] && kill "$(cat "$j/pid")" 2>/dev/null; rm -rf -- "$j" rmdir "$DIR" 2>/dev/null echo "left=$(ls "$DIR" 2>/dev/null | wc -l) loops=$(cat /sys/block/loop*/loop/backing_file 2>/dev/null | grep -c "/chjtest-")" ;; cleanup-stale) # detach loops still bound to deleted CH-J test images of earlier runs (nothing else) n=0 for sys in /sys/block/loop*; do b=$(cat "$sys/loop/backing_file" 2>/dev/null) case $b in "$DIR"/chjtest-[a-z0-9]*.img\ \(deleted\)) ;; *) continue ;; esac for m in $(awk -v d="/dev/${sys##*/}" 'index($1, d)==1 && $2 ~ /^\/mnt\/chj-disks-test-/ { print $2 }' /proc/mounts); do umount "$m"; done losetup -d "/dev/${sys##*/}" && n=$((n + 1)) done echo "detached=$n loops=$(cat /sys/block/loop*/loop/backing_file 2>/dev/null | grep -c "/chjtest-")" ;; *) die ARGS op ;; esac