. "$BST_LIB" # BST-TIMEOUT: 300 # measured 32s for 49 cases (==BST-SUITE, 2026-08-18, local # # verify on a loaded build host); ~9x headroom. The suite # # execs the probe eleven times and each run parses the whole # # tree, so the cost is exec-bound rather than data-bound and # # scales with cases added, not with fixture size. # libdvdread 6.1.3 (meta-oe recipes-multimedia/libdvdread) -- VideoLAN's # DVD-Video access library, shipped here as /usr/lib/libdvdread.a, the ten # dvdread/ headers and dvdread.pc. It is a LIBRARY port with no program of its # own, so this suite is driven entirely by one consumer the bbappend builds # against this image's own archive and packages as libdvdread-tests: # # /usr/libexec/libdvdread-tests/libdvdread-probe # # THE ORACLE IS A STORED x86-64 REFERENCE, AND THE ARGUMENT FOR IT IS STRONGER # HERE THAN FOR ANY DECODER ON THIS IMAGE. 57-libmad's reference needed a # paragraph about fixed-point arithmetic; 57-libvorbis and 57-speexdsp refuse a # cross-machine digest outright because they decode in floating point. # libdvdread computes nothing. It is a PARSER: every number it reports is a # field lifted out of a byte at a fixed offset and byte-swapped, every structure # is __attribute__((packed)) with fixed-width members, and there is not one # floating-point operation and not one arithmetic kernel in the library. Two # machines that agree on what the bytes say MUST produce identical output, and a # difference is a real defect -- a missed byte swap, a packed attribute the # compiler dropped, a struct whose size moved -- rather than drift to be # re-referenced. Do not re-pin these references to whatever the arm printed. # # WHAT THE FIXTURE IS, AND WHY IT IS SYNTHESISED. data/libdvdread-*.IFO/BUP/VOB # are a minimal but STRUCTURALLY VALID DVD-Video tree written by # src/libdvdread_mkfixtures.c: a VMGI with a First Play PGC, a title search # pointer table and a title-set attribute table; a VTSI with a PTT search table, # a program chain table with one PGC (program map, cell playback, cell position # and a command table), a cell address table and a VOBU address map; and an # 8-sector VOB whose blocks each carry a distinct deterministic pattern. A real # DVD-Video sample cannot be bundled -- the smallest is hundreds of megabytes and # every one that exists is somebody's film -- and it would not be a better test: # what this port has to get right is the NAVIGATION STRUCTURES, and those are # exactly what the generator writes, field by field, out of libdvdread's own # ifo_types.h through offsetof() and sizeof(). There is no video content and # none is needed; libdvdread never looks inside a VOB. # # THE SUITE STAGES THE TREE INTO /tmp AND THAT IS NOT ONLY THE READ-ONLY ROOT. # DVDOpen on a directory needs a VIDEO_TS/ subdirectory holding files with their # real DVD names, and data/ is installed flat (the packager installs data/* with # no recursion), so the five payloads ship under libdvdread- prefixed names and # are copied into place here. This also means the corruption controls below # operate on copies and cannot damage the shipped payload. # # READING A RED. # * libdvdread/probe-present skip -> libdvdread-tests is not installed; every # case below is absent rather than failing, and the image entry is what to # fix. # * a libdvdread/size-* red -> the packed struct layout differs between the # machine that wrote the fixture and this one. EVERY other case here is # explained by that and is not independent evidence; fix that first. # * libdvdread/self-block red with every size-* green -> a constant or an # offset moved: read the diff, it names the field. # * libdvdread/dvd-block red with the sizes green -> a genuine parse # difference. The named cases below (discid, the per-block digests, the # cell and VOBU tables) narrow it; the block diff is authoritative. # * libdvdread/ifoprint-* red with dvd-block green -> ifo_print.c's rendering, # not the parse: a printf width or a formatting path, not a wrong value. # * libdvdread/no-css red -> the library did NOT take the non-CSS input path. # Either dlopen started working (which would be news, and good news) or a # libdvdcss appeared on the image. Everything else here still holds; the # reference does not. # * libdvdread/mutation-detected or libdvdread/corrupt-both-rejected red -> # this suite's own controls are not discriminating and NOTHING above can be # trusted, whatever colour it is. DVDDIR=/usr/libexec/libdvdread-tests PROBE=$DVDDIR/libdvdread-probe if [ ! -x "$PROBE" ]; then bst_skip libdvdread/probe-present "libdvdread-tests not installed on this image" exit 0 fi tmp=${TMPDIR:-/tmp}/bst-libdvdread.$$ mkdir -p "$tmp" 2>/dev/null # Pull one named section out of the x86-64 control. want_section() { awk -v s="== $1" '$0==s{p=1;next} /^== /{p=0} p' "$BST_DATA/libdvdread-native.txt" } # Compare a probe run against its section of the control, byte for byte. `D ` # lines are diagnostics whose value differs between the two machines by # construction (sizeof(long) and friends) and are dropped on both sides -- the # control was written with the same filter. cmp_section() { _id=$1; _sec=$2; shift 2 _got=$tmp/$_sec.got _wnt=$tmp/$_sec.want "$@" 2>&1 | grep -v '^D ' > "$_got" want_section "$_sec" > "$_wnt" if [ ! -s "$_wnt" ]; then bst_notok "$_id" "no '== $_sec' section in libdvdread-native.txt" elif cmp -s "$_got" "$_wnt"; then bst_ok "$_id" else bst_notok "$_id" "$(_bst_1line "$(diff "$_wnt" "$_got" 2>/dev/null || cat "$_got")")" fi } # ------------------------------------------------------------ what shipped --- check libdvdread/lib-static test -f /usr/lib/libdvdread.a check libdvdread/header-main test -f /usr/include/dvdread/dvd_reader.h check libdvdread/header-ifo test -f /usr/include/dvdread/ifo_types.h check libdvdread/header-udf test -f /usr/include/dvdread/dvd_udf.h check libdvdread/header-nav test -f /usr/include/dvdread/nav_read.h check libdvdread/pkgconfig test -f /usr/lib/pkgconfig/dvdread.pc check_out libdvdread/pkgconfig-version "Version: 6.1.3" cat /usr/lib/pkgconfig/dvdread.pc # 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 libdvdread/no-shared "/usr/lib/libdvdread.so" # 6.1.3 ships NO dvdread-config -- upstream deleted it in the 5.x build-system # rewrite, so the stock recipe's `inherit binconfig` is vestigial and # libdvdread-bin is an empty package. Asserted rather than assumed: if a future # version restores the script, the image entry has to gain libdvdread-bin, and # this case is where that gets noticed instead of in a puzzled reading of # lib_package. check libdvdread/no-binconfig test ! -e /usr/bin/dvdread-config check libdvdread/probe-present test -x "$PROBE" # ------------------------------------------------- the identities, one by one -- # Every parse case below rests on these. They are named individually so a red # says WHICH structure moved rather than only that the block differs. self=$("$PROBE" self 2>&1); rc=$? if [ "$rc" -ne 0 ]; then if bst_execfail "$self"; then bst_unrun libdvdread/probe-self "$self" else bst_notok libdvdread/probe-self "rc=$rc $(_bst_1line "$self")"; fi else bst_ok libdvdread/probe-self fi sfield() { printf '%s\n' "$self" | awk -v k="$1" -v f="$2" '$1==k && $2==f {print $3; exit}'; } check_eq libdvdread/version "6.1.3" "$(sfield V version)" check_eq libdvdread/size-vmgi_mat "510" "$(sfield Z vmgi_mat)" check_eq libdvdread/size-vtsi_mat "984" "$(sfield Z vtsi_mat)" check_eq libdvdread/size-vts_attrs "542" "$(sfield Z vts_attributes)" check_eq libdvdread/size-title_info "12" "$(sfield Z title_info)" check_eq libdvdread/size-cell_pb "24" "$(sfield Z cell_playback)" check_eq libdvdread/size-cell_adr "12" "$(sfield Z cell_adr)" check_eq libdvdread/size-audio_attr "8" "$(sfield Z audio_attr)" check_eq libdvdread/off-tt_srpt "196" "$(sfield O vmgi.tt_srpt)" check_eq libdvdread/off-vts_pgcit "204" "$(sfield O vtsi.vts_pgcit)" check_eq libdvdread/lb-len "2048" "$(sfield C DVD_VIDEO_LB_LEN)" cmp_section libdvdread/self-block self "$PROBE" self # ---------------------------------------------------------- stage the tree --- dvd=$tmp/dvd mkdir -p "$dvd/VIDEO_TS" 2>/dev/null staged=1 for f in VIDEO_TS.IFO VIDEO_TS.BUP VTS_01_0.IFO VTS_01_0.BUP VTS_01_1.VOB; do if [ ! -f "$BST_DATA/libdvdread-$f" ]; then bst_notok libdvdread/payload "missing $BST_DATA/libdvdread-$f" staged=0 break fi cp "$BST_DATA/libdvdread-$f" "$dvd/VIDEO_TS/$f" 2>/dev/null || staged=0 done if [ "$staged" -ne 1 ]; then bst_notok libdvdread/stage "cannot stage the VIDEO_TS tree under $tmp" rm -rf "$tmp" exit 0 fi bst_ok libdvdread/stage # ------------------------------------------------------------- the parse ---- out=$tmp/dvd.out "$PROBE" dvd "$dvd" > "$out" 2>&1; prc=$? if [ "$prc" -ne 0 ]; then if bst_execfail "$(cat "$out")"; then bst_unrun libdvdread/probe-dvd "$(cat "$out")" else bst_notok libdvdread/probe-dvd "rc=$prc $(_bst_1line "$(cat "$out")")"; fi else bst_ok libdvdread/probe-dvd fi pfield() { awk -v k="$1" -v f="$2" '$1==k && $2==f {print $3; exit}' "$out"; } # The three things the ticket named, each its own case. check_out libdvdread/open "R open ok" cat "$out" check_out libdvdread/ifo-vmg-id "M id DVDVIDEO-VMG" cat "$out" check_out libdvdread/ifo-vts-id "S id DVDVIDEO-VTS" cat "$out" # DVDDiscID is the only exported path through libdvdread's own md5.c, and it # hashes the IFO files whole -- so it is a single value that goes wrong if any # byte of either navigation file is read differently. check_eq libdvdread/discid "84d89516efa6740e1ac63b3f83a86ab7" "$(pfield R discid)" # The navigation tables, read back as values rather than as a block. check_eq libdvdread/tt-srpts "1" "$(pfield T nr_of_srpts)" check_eq libdvdread/vts-atrt-count "1" "$(pfield A nr_of_vtss)" check_eq libdvdread/pgci-count "1" "$(pfield G nr_of_pgci_srp)" check_eq libdvdread/vob-filesize "8" "$(pfield B filesize)" check_out libdvdread/cell-address "C cell0 vob 1 cell 1 start 0 last 7" cat "$out" check_out libdvdread/vobu-admap "U vobu 1 4" cat "$out" # DVDReadBlocks, one block and then a run: a reader that ignores its count # argument passes the single-block case and fails this pair. check_out libdvdread/block-0 "B block 0 BRINTOS-DVD-BLOCK-00 fnv 2ec6e34fa69af218" cat "$out" check_out libdvdread/block-7 "B block 7 BRINTOS-DVD-BLOCK-07 fnv 77faea10a1540d34" cat "$out" check_out libdvdread/block-run "B run 0-7 got 8 fnv bb61806375973b1b" cat "$out" # A read entirely past the end must return 0, not invent data. check_out libdvdread/block-past-end "B past-end got 0" cat "$out" # DVDFileSeek + DVDReadBytes is the other access path -- it is what ifoRead_* # uses internally -- and seeking to 4096 must land in block 2. check_out libdvdread/seek-bytes "B seek4096 got 64 BRINTOS-DVD-BLOCK-02" cat "$out" # The input backend, in the library's own words. dvd_input.c says this exactly # when dlopen("libdvdcss.so.2") returned NULL and it installed its own file_* # readers -- the unencrypted path this port is scoped to. check_out libdvdread/no-css "Encrypted DVD support unavailable." cat "$out" cmp_section libdvdread/dvd-block dvd "$PROBE" dvd "$dvd" # ifo_print.c is roughly a thousand lines that render the same structures a # second time through a completely separate code path, and nothing else on this # image reaches it. Compared as two blocks rather than named case by case. cmp_section libdvdread/ifoprint-vmg ifoprint-0 "$PROBE" print "$dvd" 0 cmp_section libdvdread/ifoprint-vts ifoprint-1 "$PROBE" print "$dvd" 1 # The one line in those two blocks worth naming on its own, because it is the # only place this port carries a patch. Stock ifoPrint_C_ADT() sizes the cell # address table by sizeof(c_adt_t) -- a struct ending in a pointer -- instead of # sizeof(cell_adr_t), so on a 64-bit build it prints three quarters of the # entries and, for a table this small, none. wasm32's 4-byte pointer makes the # two sizes equal, so THIS TARGET was always right and the patch changes nothing # here: what it fixes is the machine the reference is generated on. The case is # named anyway so that a red reads as "the cell entry stopped rendering" rather # than as an unexplained block diff, and so that a future reference taken from an # unpatched tree is caught by a case that says why. # # THE NEEDLE IS "Sector (first)", AND THE OBVIOUS ONE WAS WRONG. The first cut # grepped for "VOB ID: 1, Cell ID: 1" and PASSED against an unpatched 64-bit # library -- because the PGC's cell-position printer emits "Cell: 1 has VOB # ID: 1, Cell ID: 1" twenty lines earlier, and a substring match cannot tell # the two apart. The case was asserting the presence of a line the bug does not # touch. "Sector (first)" is printed by ifoPrint_C_ADT and by nothing else in # either dump, which is what makes this a guard rather than decoration. check_out libdvdread/ifoprint-cell-entry \ "VOB ID: 1, Cell ID: 1 Sector (first): 0x00000000 (last): 0x00000007" \ "$PROBE" print "$dvd" 1 # ------------------------------------------------------- negative controls --- # A guard adopted because it passes is not evidence. These four establish that # the comparisons above would actually go red if the library were wrong, and # that the failure paths mean what the cases assume. # 1. Corrupt the VTSI's identifier. ifoOpen must fall back to VTS_01_0.BUP and # still succeed -- the backup path is a real feature of the format and is # otherwise never taken on a healthy tree. bup=$tmp/bup mkdir -p "$bup/VIDEO_TS" 2>/dev/null cp "$dvd"/VIDEO_TS/* "$bup/VIDEO_TS/" 2>/dev/null if printf 'XXXXXXXXXXXX' | dd of="$bup/VIDEO_TS/VTS_01_0.IFO" bs=1 count=12 \ conv=notrunc 2>/dev/null; then bupout=$("$PROBE" open "$bup" 2>&1) case "$bupout" in *"R vtsi ok"*) bst_ok libdvdread/bup-fallback ;; *) bst_notok libdvdread/bup-fallback "a corrupt VTS_01_0.IFO did not fall back to the BUP: $(_bst_1line "$bupout")" ;; esac else bst_notok libdvdread/bup-fallback "cannot stage the corrupted copy" fi # 2. Corrupt BOTH. ifoOpen must now report failure -- cleanly, with the process # still alive to say so. This is what proves case 1 was the BUP being read # and not the corruption being ignored. if printf 'XXXXXXXXXXXX' | dd of="$bup/VIDEO_TS/VTS_01_0.BUP" bs=1 count=12 \ conv=notrunc 2>/dev/null; then bothout=$("$PROBE" open "$bup" 2>&1) case "$bothout" in *"R vtsi FAILED"*) bst_ok libdvdread/corrupt-both-rejected ;; *) bst_notok libdvdread/corrupt-both-rejected "a VTSI corrupt in both copies still parsed -- the identifier check is not running, and libdvdread/bup-fallback proves nothing: $(_bst_1line "$bothout")" ;; esac else bst_notok libdvdread/corrupt-both-rejected "cannot stage the corrupted copy" fi # 3. Mutate one VOB block. Every block digest above must then differ -- if a # mutated payload still hashes to the reference, the digests are asserting # nothing. Byte 100 of block 3 is inside the pattern, not inside the ASCII # tag, so the tag still prints and only the digest moves. mut=$tmp/mut mkdir -p "$mut/VIDEO_TS" 2>/dev/null cp "$dvd"/VIDEO_TS/* "$mut/VIDEO_TS/" 2>/dev/null printf '\125' | dd of="$mut/VIDEO_TS/VTS_01_1.VOB" bs=1 seek=6244 count=1 \ conv=notrunc 2>/dev/null if cmp -s "$mut/VIDEO_TS/VTS_01_1.VOB" "$dvd/VIDEO_TS/VTS_01_1.VOB"; then # Distinguished from a wrong digest on purpose: the copy being IDENTICAL means # either dd did not write or byte 6244 already held 0x55, and a control that # mutates nothing would pass forever while asserting nothing. If the fixture is # ever regenerated this offset has to be re-checked against it. bst_notok libdvdread/mutation-detected "the mutated copy is byte-identical to the fixture -- dd did not write, or offset 6244 already held 0x55; this control is asserting nothing" else mutfnv=$("$PROBE" dvd "$mut" 2>&1 | awk '$1=="B" && $2=="block" && $3=="3" {print $6; exit}') reffnv=$(awk '$1=="B" && $2=="block" && $3=="3" {print $6; exit}' "$out") if [ -z "$mutfnv" ]; then bst_notok libdvdread/mutation-detected "the probe printed no digest for the mutated block" elif [ "$mutfnv" = "$reffnv" ]; then bst_notok libdvdread/mutation-detected "a mutated VOB block hashed to the reference ($mutfnv) -- the block digests above prove nothing" else bst_ok libdvdread/mutation-detected fi fi # 4. A directory with no VIDEO_TS at all. DVDOpen returns a reader for any # directory -- it is only when a file is asked for that the absence shows -- # so the assertion is that the IFO open FAILS rather than that the open does. empty=$tmp/empty mkdir -p "$empty" 2>/dev/null emptyout=$("$PROBE" open "$empty" 2>&1) case "$emptyout" in *"R vmgi FAILED"*) bst_ok libdvdread/empty-tree-rejected ;; *) bst_notok libdvdread/empty-tree-rejected "an empty directory yielded a VMGI: $(_bst_1line "$emptyout")" ;; esac rm -rf "$tmp"