# 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"