# BST-TIMEOUT: 600 # ~70 cases, most of them a process spawn; the default 60s is short here
. "$BST_LIB"
# Berkeley DB 5.3.28 -- oe-core's db (poky meta/recipes-support/db): the
# embedded key/value store Sleepycat wrote, still the storage engine under a
# very large amount of software. The port ships libdb-5.3.a, the headers and
# the db_* command-line utilities; those utilities are what this suite drives,
# because they exercise the same access-method code a linked program would and
# they are what a user of this image actually has.
#
# ---------------------------------------------------------------------------
# WHAT THIS SUITE ASSERTS, AND WHY IT IS SHAPED AROUND STANDALONE FILES
#
# Berkeley DB has two ways to open a database, and they land on opposite sides
# of this platform's known gaps:
#
# 1. STANDALONE. db_load/db_dump/db_verify/db_stat with no -h create a
# PRIVATE environment internally -- DB_CREATE|DB_INIT_MPOOL|DB_PRIVATE --
# whose regions are malloc'd rather than mapped, and which takes no
# fcntl(2) record lock. Nothing on that path touches the gaps the sibling
# database ports measured, so this is where the port's functional
# coverage lives: a btree and a hash database each loaded, dumped back,
# verified and counted.
#
# 2. SHARED ENVIRONMENT (-h
). Its regions are region FILES mapped
# MAP_SHARED, and the environment is protected by fcntl locking -- which
# is what brintos.io/brintos/linux/issues/16 (fcntl record locking ->
# EINVAL) and issues/30 (mmap(MAP_SHARED) with PROT_WRITE -> ENODEV)
# refuse. The suite PROBES it and computes its verdict from this run
# rather than hard-coding one, so the day either issue lands this suite
# notices by itself.
#
# EVERY EXPECTATION HERE CAME OFF A NATIVE x86_64 CONTROL -- the identical
# 5.3.28 tree with the identical seven oe-core patches and the identical
# configure line, built and run before this suite shipped. That is where the
# non-obvious ones come from: db_verify is NOT silent on success (it prints
# `BDB5105 Verification of succeeded.`), a hash dump comes back in
# bucket order rather than key order, and db_stat words the same two counts
# differently per access method ("unique keys in the tree" / "data items in
# the tree" against "keys in the database" / "data items in the database").
#
# The round trip is compared in the guest with cmp(1), against the very file
# that was loaded -- so nothing long is shipped through the console and no
# expected dump is transcribed into this file to rot.
# ---------------------------------------------------------------------------
R="${BST_DB_ROOT:-}"
BIN=$R/usr/bin
ARCHIVE=$R/usr/lib/libdb-5.3.a
if [ ! -x "$BIN/db_load" ] && [ ! -f "$ARCHIVE" ]; then
bst_skip db/present "db not installed on this image"
exit 0
fi
bst_ok db/present
# --- 1. the package is whole -------------------------------------------------
#
# ${PN}-bin is the utilities, -dev the headers, -staticdev the archive. The
# four the requirements name are asserted first; the rest of the set follows,
# because oe-core FILES them all into one package and a missing one means a
# packaging split went wrong rather than that the platform refused anything.
for t in db_load db_dump db_verify db_stat; do
check "db/bin-$t" test -x "$BIN/$t"
done
for t in db_archive db_checkpoint db_deadlock db_hotbackup db_log_verify \
db_printlog db_recover db_replicate db_tuner db_upgrade; do
check "db/bin-$t" test -x "$BIN/$t"
done
check db/header test -f "$R/usr/include/db.h"
check db/header-db51 test -f "$R/usr/include/db51/db.h"
check db/staticlib test -f "$ARCHIVE"
# The bbappend selects PACKAGECONFIG "verify" precisely so oe-core's
# do_install:append does not delete db_verify. Its absence would fail no build
# and no other case here.
if [ -x "$BIN/db_verify" ]; then bst_ok db/verify-not-deleted
else bst_notok db/verify-not-deleted "db_verify absent -- PACKAGECONFIG 'verify' is not in effect"; fi
# Shape, not absence: this arm converts to shared libraries per recipe
# (BRINTOS-1740), so a .so here is a conversion, not a policy breach.
bst_lib_shape db/no-shared "$R/usr/lib/libdb-5.3.so" "$R/usr/lib/libdb.so"
# --- 2. the tools answer at all ----------------------------------------------
# -V prints the release and exits without opening anything.
for t in db_load db_dump db_stat db_verify; do
check_out "db/version-$t" "Berkeley DB 5.3.28" "$BIN/$t" -V
done
# --- 3. the fixture ----------------------------------------------------------
work=${TMPDIR:-/tmp}/bst-db.$$
rm -rf "$work"
mkdir -p "$work" || { bst_notok db/workdir "cannot create $work"; exit 0; }
# db_load -T reads flat text: one line per key, one per value, alternating.
# Eight pairs, written in byte order so a btree dump comes back in this exact
# sequence and needs no sorting step to get wrong.
cat > "$work/in.txt" <<'EOT'
alpha
value-01-db
bravo
value-02-db
charlie
value-03-db
delta
value-04-db
echo
value-05-db
foxtrot
value-06-db
golf
value-07-db
hotel
value-08-db
EOT
NPAIRS=8
# pairs -> "key=value" lines, sorted. Used for the hash dump, whose
# order is the hash function's business and not ours.
pairs() {
awk 'NR % 2 == 1 { k = $0; next } { print k "=" $0 }' "$1" | sort
}
# dumped -> the flat text that produced it: the
# lines between HEADER=END and DATA=END, each with db_dump's leading space
# removed.
dumped() {
sed -n '/^HEADER=END$/,/^DATA=END$/p' "$1" | sed '1d;$d' | sed 's/^ //' > "$2"
}
# statcount -> the count db_stat printed on the
# line describing it. db_stat prints "\t" and words the
# description differently per access method, so the needle is passed in.
statcount() {
awk -v want="$2" 'index($0, want) { print $1; exit }' "$1"
}
# --- 4. btree and hash, each loaded / dumped / verified / counted ------------
#
# The two access methods are separate code paths in Berkeley DB -- a B+tree
# over sorted pages against a bucket array with overflow chains -- so the whole
# sequence runs twice rather than once, which is what the requirements ask for.
for m in btree hash; do
db=$work/$m.db
out=$work/$m.dump
rt=$work/$m.rt
"$BIN/db_load" -T -t "$m" -f "$work/in.txt" "$db" > "$work/$m.load" 2>&1; rc=$?
if [ "$rc" -eq 0 ]; then bst_ok "db/$m-load"
else bst_notok "db/$m-load" "db_load -T -t $m rc=$rc $(_bst_1line "$(cat "$work/$m.load")")"; fi
if [ -s "$db" ]; then bst_ok "db/$m-file-nonempty"
else bst_notok "db/$m-file-nonempty" "$db is absent or empty after db_load"; fi
"$BIN/db_dump" -p "$db" > "$out" 2> "$work/$m.dump.err"; rc=$?
if [ "$rc" -eq 0 ]; then bst_ok "db/$m-dump"
else bst_notok "db/$m-dump" "db_dump -p rc=$rc $(_bst_1line "$(cat "$work/$m.dump.err")")"; fi
# The dump's header states the access method found IN THE FILE, which is the
# one assertion that db_load -t was honoured rather than defaulted.
check_out "db/$m-dump-type" "type=$m" cat "$out"
# THE ROUND TRIP. btree comes back in key order, which is the order the
# fixture is written in, so it is compared as-is; hash comes back in bucket
# order, so both sides are paired and sorted first.
dumped "$out" "$rt"
if [ "$m" = btree ]; then
if cmp -s "$work/in.txt" "$rt"; then bst_ok "db/$m-roundtrip"
else bst_notok "db/$m-roundtrip" "dump differs from the loaded set: $(_bst_1line "$(cat "$rt")")"; fi
else
pairs "$work/in.txt" > "$work/$m.want"
pairs "$rt" > "$work/$m.got"
if cmp -s "$work/$m.want" "$work/$m.got"; then bst_ok "db/$m-roundtrip"
else bst_notok "db/$m-roundtrip" "dump differs from the loaded set: $(_bst_1line "$(cat "$work/$m.got")")"; fi
fi
# Every pair individually, so a single corrupted record names itself instead
# of hiding inside one aggregate diff.
i=1
while [ "$i" -le "$NPAIRS" ]; do
k=$(awk -v n="$i" 'NR == 2*n-1 { print; exit }' "$work/in.txt")
v=$(awk -v n="$i" 'NR == 2*n { print; exit }' "$work/in.txt")
got=$(awk -v k="$k" '$0 == k { getline; print; exit }' "$rt")
check_eq "db/$m-pair-$k" "$v" "$got"
i=$((i + 1))
done
# db_verify: the only structural check Berkeley DB offers, and the reason the
# bbappend selects PACKAGECONFIG "verify". It exits 0 AND says so -- the
# native control prints `BDB5105 Verification of succeeded.`, so a
# silent exit 0 would itself be a change worth failing on.
check_out "db/$m-verify" "succeeded" "$BIN/db_verify" "$db"
# db_stat -d: the record count, read off the line whose wording matches this
# access method.
"$BIN/db_stat" -d "$db" > "$work/$m.stat" 2>&1; rc=$?
if [ "$rc" -eq 0 ]; then bst_ok "db/$m-stat"
else bst_notok "db/$m-stat" "db_stat -d rc=$rc $(_bst_1line "$(cat "$work/$m.stat")")"; fi
case "$m" in
btree) knee="unique keys in the tree"; dnee="data items in the tree" ;;
hash) knee="keys in the database"; dnee="data items in the database" ;;
esac
check_eq "db/$m-stat-keys" "$NPAIRS" "$(statcount "$work/$m.stat" "$knee")"
check_eq "db/$m-stat-records" "$NPAIRS" "$(statcount "$work/$m.stat" "$dnee")"
check_out "db/$m-stat-magic" "magic number" cat "$work/$m.stat"
done
# --- 5. refusals -------------------------------------------------------------
# A file that is not a database must be refused rather than read as one, and a
# truncated one must fail verification rather than exiting 0. Both exit 1 on
# the native control.
printf 'this is not a Berkeley DB file\n' > "$work/garbage"
check_rc db/dump-refuses-garbage 1 "$BIN/db_dump" "$work/garbage"
check_rc db/verify-refuses-garbage 1 "$BIN/db_verify" "$work/garbage"
dd if="$work/btree.db" of="$work/truncated.db" bs=1 count=2048 2>/dev/null
check_rc db/verify-refuses-truncated 1 "$BIN/db_verify" "$work/truncated.db"
# --- 6. the shared-environment path, measured rather than assumed ------------
#
# Everything above ran in a PRIVATE environment. Two separate questions live
# here, and the obvious probe answers neither of them -- which is why this
# group is shaped the way it is.
#
# `db_load -h ` on a directory that holds no environment does NOT create a
# shared one. db_load's db_init() tries a shared open first, and on failure
# FALLS BACK to DB_CREATE|DB_INIT_MPOOL|DB_PRIVATE. So it succeeds on the
# native x86_64 control and on this platform alike, and it succeeds for the
# same reason both times: the fallback. Asserting it and calling the result "a
# shared environment works" would have been a false claim in the port's own
# evidence -- the give-away is that the directory afterwards holds the database
# and NO __db.00N region files.
#
# The genuine probe is db_recover -e, which opens with DB_CREATE|DB_INIT_LOG|
# DB_INIT_LOCK|DB_INIT_MPOOL|DB_INIT_TXN and no DB_PRIVATE, and leaves the
# region files behind. THAT is what maps the region files MAP_SHARED with
# PROT_WRITE, which brintos.io/brintos/linux/issues/30 refuses. On the native
# control it exits 0 and produces __db.001..__db.003; here it creates the files
# and then dies mapping them. The verdict is COMPUTED from this run, so the day
# issue 30 lands this suite notices by itself.
ISSUE_ENV="held by brintos.io/brintos/linux/issues/30 (mmap(MAP_SHARED) of a regular file fails whenever PROT_WRITE is requested). db_recover names the errno outright -- 'BDB0126 mmap: No such device' (ENODEV) then 'DB_ENV->open: No such device' -- where the reporting gdbm port saw only its own 'Malloc error'. issues/16 (fcntl record locking -> EINVAL) is NOT implicated: the mapping fails first, so the lock is never attempted. The private environment the standalone utilities fall back to maps nothing, which is why every case above passes"
# --- 6a. the private fallback: what -h actually gives you here ---------------
envdir=$work/env
mkdir -p "$envdir"
"$BIN/db_load" -h "$envdir" -T -t btree -f "$work/in.txt" env.db > "$work/env.load" 2>&1; rc=$?
if [ "$rc" -eq 0 ]; then
bst_ok db/env-fallback-load
"$BIN/db_dump" -h "$envdir" -p env.db > "$work/env.dump" 2> "$work/env.dump.err"; rc=$?
if [ "$rc" -eq 0 ]; then
bst_ok db/env-fallback-dump
dumped "$work/env.dump" "$work/env.rt"
if cmp -s "$work/in.txt" "$work/env.rt"; then bst_ok db/env-fallback-roundtrip
else bst_notok db/env-fallback-roundtrip "dump differs from the loaded set"; fi
else
bst_notok db/env-fallback-dump "db_dump -h rc=$rc $(_bst_1line "$(cat "$work/env.dump.err")")"
bst_skip db/env-fallback-roundtrip "db_dump -h did not run"
fi
else
bst_notok db/env-fallback-load "db_load -h rc=$rc $(_bst_1line "$(cat "$work/env.load")")"
bst_skip db/env-fallback-dump "db_load -h did not run"
bst_skip db/env-fallback-roundtrip "db_load -h did not run"
fi
# The case that keeps the one above honest: it must NOT have built a shared
# environment. If region files appear here, db_load stopped falling back and
# every reading of this group changes.
if ls "$envdir"/__db.0* >/dev/null 2>&1; then
bst_notok db/env-fallback-no-regions "db_load -h created region files -- this is no longer the private fallback path"
else
bst_ok db/env-fallback-no-regions
fi
# --- 6b. a REAL shared environment ------------------------------------------
shdir=$work/shenv
mkdir -p "$shdir"
"$BIN/db_recover" -e -h "$shdir" > "$work/shenv.rec" 2>&1; rc=$?
regions=no
ls "$shdir"/__db.0* >/dev/null 2>&1 && regions=yes
if [ "$rc" -eq 0 ] && [ "$regions" = yes ]; then
bst_ok db/env-shared-create
bst_ok db/env-shared-regions
check_out db/env-shared-stat "Environment version" "$BIN/db_stat" -e -h "$shdir"
# Joining an environment that already exists is the path db_load takes when
# the fallback is NOT reached -- the one this platform has never run.
"$BIN/db_load" -h "$shdir" -T -t btree -f "$work/in.txt" shared.db > "$work/shenv.load" 2>&1; rc=$?
if [ "$rc" -eq 0 ]; then
bst_ok db/env-shared-join
"$BIN/db_dump" -h "$shdir" -p shared.db > "$work/shenv.dump" 2>&1; rc=$?
if [ "$rc" -eq 0 ]; then
dumped "$work/shenv.dump" "$work/shenv.rt"
if cmp -s "$work/in.txt" "$work/shenv.rt"; then bst_ok db/env-shared-roundtrip
else bst_notok db/env-shared-roundtrip "dump differs from the loaded set"; fi
else
bst_notok db/env-shared-roundtrip "db_dump -h rc=$rc $(_bst_1line "$(cat "$work/shenv.dump")")"
fi
else
bst_notok db/env-shared-join "db_load into an existing shared environment rc=$rc $(_bst_1line "$(cat "$work/shenv.load")")"
bst_skip db/env-shared-roundtrip "db_load into the shared environment did not run"
fi
else
# THE FINDING, stated as what a user would see. db_recover -e exits 0 and
# leaves __db.001..003 on the native control.
bst_notok db/env-shared-create \
"db_recover -e -h cannot build a shared environment (rc=$rc, region files created: $regions): $(_bst_1line "$(cat "$work/shenv.rec")"); $ISSUE_ENV"
for c in env-shared-regions env-shared-stat env-shared-join env-shared-roundtrip; do
bst_skip "db/$c" "$ISSUE_ENV"
done
fi
rm -rf "$work"