git mirror - github.com/owenewans/holy - branch master
clone: https://src.holypkg.eu/holy/

file tests/directories.sh

#!/bin/sh
set -eu
bin=$(realpath "$1")
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
umask 022
expect() {
    want=$1
    shift
    if "$@" > "$tmp/out" 2> "$tmp/err"; then got=0; else got=$?; fi
    if test "$got" -ne "$want"; then
        printf 'expected %s, got %s: %s\n' "$want" "$got" "$*" >&2
        cat "$tmp/out" "$tmp/err" >&2
        exit 1
    fi
}
for version in old new; do
    tree="$tmp/$version"
    mkdir -p "$tree/HOLY" "$tree/DATA/usr/share"
    printf 'format holy-package-1\nname directories\nversion %s\nrelease 1\nos linux\narch noarch\nlibc nolibc\n' "$version" > "$tree/HOLY/meta"
    for part in deps provides hooks origin transform; do : > "$tree/HOLY/$part"; done
    printf '%s\n' "$version" > "$tree/DATA/usr/share/payload"
    if test "$version" = new; then
        mkdir -p "$tree/DATA/opt/apps/deep"
        printf 'nested payload\n' > "$tree/DATA/opt/apps/deep/data"
        ln -s data "$tree/DATA/opt/apps/deep/link"
        chmod 750 "$tree/DATA/opt/apps"
    fi
    expect 0 "$bin" manifest generate "$tree" --output "$tmp/files"
    mv "$tmp/files" "$tree/HOLY/files"
    expect 0 "$bin" pack "$tree" --output "$tmp/$version.holy"
done
old=$(sha256sum "$tmp/old.holy" | cut -d ' ' -f 1)
new=$(sha256sum "$tmp/new.holy" | cut -d ' ' -f 1)
initialize() {
    root=$1
    mkdir "$root"
    expect 0 "$bin" db init --root "$root"
    expect 0 "$bin" cache stage "local:$tmp/old.holy" --root "$root"
    expect 0 "$bin" cache stage "local:$tmp/new.holy" --root "$root"
}
plan_set() {
    expect 0 "$bin" db plan-set "$1" --root "$root"
    plan=$(sed -n 's/^plan-set .* sha256 \([0-9a-f]*\) read-only$/\1/p' "$tmp/out")
    test "${#plan}" -eq 64
}
check_tree() {
    expect 0 "$bin" db check --all --root "$root"
    grep -qx 'nested payload' "$root/opt/apps/deep/data"
    test "$(stat -c %a "$root/opt")" = 755
    test "$(stat -c %a "$root/opt/apps")" = 750
    test "$(readlink "$root/opt/apps/deep/link")" = data
    test "$(find "$root" -name '.holy-dir-*' | wc -l)" -eq 0
}
initialize "$tmp/single"
expect 0 "$bin" db reserve "$new" --root "$root"
expect 0 "$bin" db plan --root "$root"
plan=$(sed -n 's/^plan .* sha256 \([0-9a-f]*\) read-only$/\1/p' "$tmp/out")
test ! -e "$root/opt"
expect 0 "$bin" db approve "$plan" --root "$root"
umask 077
expect 0 "$bin" db apply --root "$root"
umask 022
check_tree
rm "$root/opt/apps/deep/data" "$root/opt/apps/deep/link"
rmdir "$root/opt/apps/deep" "$root/opt/apps" "$root/opt"
expect 4 "$bin" db check --all --root "$root"
expect 0 "$bin" db repair-plan "$new" --root "$root"
plan=$(sed -n 's/^repair-plan .* sha256 \([0-9a-f]*\) missing-only read-only$/\1/p' "$tmp/out")
expect 0 "$bin" db repair "$new" --plan "$plan" --root "$root"
check_tree
printf user > "$root/opt/apps/deep/untracked"
expect 0 "$bin" db rm "$new" --root "$root"
grep -qx user "$root/opt/apps/deep/untracked"
test -d "$root/opt/apps/deep"
initialize "$tmp/update"
plan_set "$old"
expect 0 "$bin" db apply-set "$plan" "$old" --root "$root"
expect 0 "$bin" db plan-update "$old" "$new" --root "$root"
plan=$(sed -n 's/^plan-update sha256 \([0-9a-f]*\) read-only$/\1/p' "$tmp/out")
test ! -e "$root/opt"
expect 0 "$bin" db apply-update "$plan" "$old" "$new" --root "$root"
check_tree
initialize "$tmp/unsafe"
plan_set "$new"
mkdir "$tmp/outside"
printf sentinel > "$tmp/outside/marker"
ln -s "$tmp/outside" "$root/opt"
expect 4 "$bin" db apply-set "$plan" "$new" --root "$root"
grep -qx sentinel "$tmp/outside/marker"
test ! -e "$tmp/outside/apps"
test ! -f "$root/var/lib/holypkg/transactions/set-journal"
rm "$root/opt"
mkdir "$root/opt"
chmod 700 "$root/opt"
expect 4 "$bin" db plan-set "$new" --root "$root"
test "$(stat -c %a "$root/opt")" = 700
# payload order must not decide whether parent creation succeeds.
lz4 -dq "$tmp/new.holy" "$tmp/new.tar"
python3 - "$tmp/new.tar" "$tmp/reordered.tar" <<'PY'
import io, sys, tarfile
with tarfile.open(sys.argv[1]) as source, tarfile.open(sys.argv[2], "w", format=tarfile.PAX_FORMAT) as output:
    members = source.getmembers()
    data = [m for m in members if m.name.startswith("DATA/")]
    order = [m for m in members if not m.name.startswith("DATA/")]
    order += [m for m in data if not m.isdir()] + list(reversed([m for m in data if m.isdir()]))
    for member in order:
        output.addfile(member, source.extractfile(member) if member.isfile() else None)
PY
lz4 -q "$tmp/reordered.tar" "$tmp/reordered.holy"
initialize "$tmp/reordered"
expect 0 "$bin" cache stage "local:$tmp/reordered.holy" --root "$root"
reordered=$(sha256sum "$tmp/reordered.holy" | cut -d ' ' -f 1)
plan_set "$reordered"
expect 0 "$bin" db apply-set "$plan" "$reordered" --root "$root"
check_tree
if "$bin" elf "$bin" | grep -q '^interpreter /'; then
    gcc -shared -fPIC -o "$tmp/fault.so" "$(dirname "$0")/update-fault.c" -ldl
    injection="LD_PRELOAD=$tmp/fault.so"
elif test "${HOLY_TEST_STATIC_UPDATE_FAULT:-0}" = 1; then
    injection="HOLY_STATIC_TEST=1"
else
    printf 'directory fault injection skipped for uninstrumented static client\n'
    printf 'directory transaction fixtures passed\n'
    exit 0
fi
for operation in set update; do
    for phase in directory-partial directory-ready directory-created payload-written; do
        if test "$operation:$phase" = update:payload-written; then continue; fi
        initialize "$tmp/$operation-$phase"
        if test "$operation" = update; then
            plan_set "$old"
            expect 0 "$bin" db apply-set "$plan" "$old" --root "$root"
            expect 0 "$bin" db plan-update "$old" "$new" --root "$root"
            plan=$(sed -n 's/^plan-update sha256 \([0-9a-f]*\) read-only$/\1/p' "$tmp/out")
            set -- db apply-update "$plan" "$old" "$new"
            recovery=--update
        else
            plan_set "$new"
            set -- db apply-set "$plan" "$new"
            recovery=--continue-set
        fi
        if env "$injection" HOLY_UPDATE_FAULT="$phase" HOLY_DIRECTORY_PARENT="$root" HOLY_DIRECTORY_NAME=opt \
            "$bin" "$@" --root "$root" > "$tmp/out" 2> "$tmp/err"; then exit 1; else test "$?" -eq 137; fi
        expect 5 "$bin" db status --root "$root"
        if test "$phase" = payload-written; then
            grep -qx 'nested payload' "$root/opt/apps/deep/data"
            printf changed > "$root/opt/apps/deep/data"
            expect 5 "$bin" db recover "$recovery" --root "$root"
            grep -qx changed "$root/opt/apps/deep/data"
            printf 'nested payload\n' > "$root/opt/apps/deep/data"
        fi
        if test "$phase" = directory-partial; then
            expect 5 "$bin" db recover "$recovery" --root "$root"
            test ! -e "$root/opt"
            stage="$root/.holy-dir-$(printf opt | sha256sum | cut -d ' ' -f 1)"
            test -d "$stage"
            test "$(stat -c %a "$stage")" = 700
            rmdir "$stage"
        fi
        expect 0 "$bin" db recover "$recovery" --root "$root"
        check_tree
    done
done
printf 'directory transaction fixtures passed\n'