. "$BST_LIB" # BRINTOS-WORKAROUND: brintos/linux-in-the-browser#138 revisit -- the FIRST line a # suite writes after the runner's ==BST-BEGIN== can be swallowed when a kernel # printk lands on the console in between, which cost 79-libevdev a scored case # that vanished from the transcript while the runner's cases= count still # included it. The failure direction is the dangerous one: a harness counting # result lines reads N of N green and is wrong about COVERAGE. This absorbs the # loss with a line nobody scores. Drop it once #138 lands. printf '#diag: libiodbc suite start (this line is a console-loss absorber, not a case)\n' # libiodbc 3.52.16 (meta-oe recipes-support/libiodbc, BRINTOS-930) -- the iODBC # DRIVER MANAGER. An application links its ODBC entry points and the manager # routes each call to whichever driver the named data source points at. # # THE SECOND ODBC DRIVER MANAGER ON THIS IMAGE. unixODBC 2.3.12 is here too # (BRINTOS-976, 77-unixodbc.test), and this suite deliberately mirrors that # one's shape so the pair reads together. They share a conclusion reached # independently: with no driver loadable on this arm, what a driver-free image # CAN establish is that every connect fails in the documented way, which is a # sharper assertion than a successful connect would be. # # WHAT IS SCORED, AND WHY EACH GROUP EXISTS. # # * PACKAGING. Both archives, the eleven headers under /usr/include/iodbc, the # pkg-config file, and the three programs. Also four NEGATIVES, each of # which is a configure flag that could silently stop taking: no /usr/local # tree (the --prefix the append removes), no libodbc.* (--disable-libodbc), # no iodbcadm-gtk (--disable-gui) and no shared library (--disable-shared). # A negative that stopped holding would not fail the build -- it would ship # a differently-shaped package -- so it is asserted here or nowhere. # # * iodbc-config. The only non-interactive program the package ships, and the # one the ticket names. --version must report the version the recipe pinned. # # * THE PROBE, groups A/B/C/U/D/E. Handle allocation and the ODBC-3 version # handshake; SQLDataSources against an empty odbc.ini and against a # two-entry one; and the IM002 contract on both SQLConnect and # SQLDriverConnect. None of this needs a driver, so all of it is scored # exactly. # # * GROUP G, THE ONE PLATFORM CASE. A source that IS configured, naming a # driver file that is not there, so the manager reaches dlopen. No ODBC # driver can exist on this arm at all while -shared does not link # (brintos.io/brintos/hwjs-cc/issues/59), which is the same wall # 77-unixodbc documents for libltdl. Only the CONTRACT is scored there -- # the manager must refuse, with a diagnostic, and not crash -- while the # SQLSTATE and message text are reported as diagnostics rather than pinned. # See the group for why pinning them would be asserting the wrong thing. # # THE ORACLE IS A NATIVE x86-64 RUN OF THE SAME PROBE AT THE SAME VERSION. # Every value below is what a native build of libiodbc_probe.c against libiodbc # 3.52.16 printed (2026-08-18). The values that legitimately DIFFER from that # oracle are F01, F02 and F03 -- the word size and the two ODBC types defined in # terms of `long` -- and each is pinned to what an ILP32 arm must produce, with # the reason beside it. require iodbc-config libiodbc/present bst_ok libiodbc/present # --- the package is whole ---------------------------------------------------- # All three programs ship from the same package, so a missing one is a broken # port rather than an absent tool -- `not ok`, never `skip`. for t in iodbc-config iodbctest iodbctestw; do if command -v "$t" >/dev/null 2>&1; then bst_ok "libiodbc/$t-present" else bst_notok "libiodbc/$t-present" "$t missing though iodbc-config is installed"; fi done check libiodbc/staticlib test -s /usr/lib/libiodbc.a check libiodbc/staticlib-inst test -s /usr/lib/libiodbcinst.a check libiodbc/pkgconfig test -s /usr/lib/pkgconfig/libiodbc.pc # The headers go under /usr/include/iodbc/ and NOT /usr/include/, which is the # whole reason this package can sit beside unixODBC: both projects ship a # sql.h. A header that appeared at the top level would be a collision, so the # location is asserted on both sides. for h in sql.h sqlext.h sqltypes.h sqlucode.h odbcinst.h iodbcinst.h iodbcunix.h; do check "libiodbc/header-$h" test -s "/usr/include/iodbc/$h" done # unixODBC's own sql.h lives at /usr/include/sql.h and is NOT ours; assert only # that iODBC did not overwrite it with a copy of its own. iODBC's carries the # OpenLink notice, unixODBC's does not. if [ -f /usr/include/sql.h ] && grep -q 'OpenLink Software' /usr/include/sql.h 2>/dev/null; then bst_notok libiodbc/no-header-collision \ "/usr/include/sql.h carries the iODBC notice -- --includedir did not take and unixODBC's header was overwritten" else bst_ok libiodbc/no-header-collision fi # --- the four negatives ------------------------------------------------------ # 1. --prefix=/usr/local removed by the bbappend. Had it not been, do_package # would have shipped nothing at all -- but a PARTIAL relapse (a stray # /usr/local left behind by some other recipe) is exactly what a build log # would not show. if [ -e /usr/local/bin/iodbc-config ] || [ -e /usr/local/lib/libiodbc.a ]; then bst_notok libiodbc/no-usr-local "libiodbc installed under /usr/local -- the --prefix removal did not take" else bst_ok libiodbc/no-usr-local fi # 2. --disable-libodbc. Without it iODBC installs a libodbc compatibility # library, which is unixODBC's name and would collide with the manager # already on this image. if [ -e /usr/lib/libodbc.a ] && grep -aq 'iODBC' /usr/lib/libodbc.a 2>/dev/null; then bst_notok libiodbc/no-libodbc-collision "/usr/lib/libodbc.a carries iODBC -- --disable-libodbc did not take and unixODBC's archive was overwritten" else bst_ok libiodbc/no-libodbc-collision fi # 3. --disable-gui. The GTK administrator is decided by a HOST probe # (AM_PATH_GTK_2_0), so a build host with GTK2 development files installed # is what makes this wobble -- it answered yes on the machine this port was # developed on. Nothing else would report that it came back. if command -v iodbcadm-gtk >/dev/null 2>&1; then bst_notok libiodbc/no-gtk-admin "iodbcadm-gtk is installed -- --disable-gui did not take and the image now wants GTK2" else bst_ok libiodbc/no-gtk-admin 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 libiodbc/no-shared "/usr/lib/libiodbc.so" "/usr/lib/libiodbc.so.2" # A real ar archive carrying the driver-manager entry points. The -staticdev # package is a link target, so an archive present but truncated would pass the # size check above and fail every consumer. check_eq libiodbc/staticlib-ar '!' "$(head -c 7 /usr/lib/libiodbc.a)" # grep rather than nm: the image ships no binutils nm, and the names are present # as plain bytes. -a where the grep supports it -- on this arm the archive # members are LLVM IR bitcode, so a grep that suppresses matches in a file # containing NULs reports nothing; GNU grep needs -a, busybox grep as shipped # does not. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi for sym in SQLAllocHandle SQLConnect SQLDriverConnect SQLDataSources \ SQLSetEnvAttr SQLGetDiagRec SQLFreeHandle; do if $_grepq "$sym" /usr/lib/libiodbc.a 2>/dev/null; then bst_ok "libiodbc/sym-$sym" else bst_notok "libiodbc/sym-$sym" "$sym absent from /usr/lib/libiodbc.a" fi done # --- iodbc-config ------------------------------------------------------------ # The version the recipe pinned, which is the case the ticket names by name. check_eq libiodbc/config-version 3.52.16 "$(iodbc-config --version 2>&1)" # --enable-odbc3 reaching the installed script, not just the library. check_eq libiodbc/config-odbcversion 3 "$(iodbc-config --odbcversion 2>&1)" # --with-iodbc-inidir=/etc, the ini location the manager compiles in. check_eq libiodbc/config-odbcini /etc/odbc.ini "$(iodbc-config --odbcini 2>&1)" # --includedir reaching the script's --cflags, which is what a consumer's build # would read. A wrong answer here builds against unixODBC's headers by accident. check_out libiodbc/config-cflags '-I/usr/include/iodbc' iodbc-config --cflags check_out libiodbc/config-libs '-liodbc' iodbc-config --libs # --- the probe --------------------------------------------------------------- PROBE=${BST_LIBIODBC_PROBE:-/usr/libexec/libiodbc-tests/libiodbc-probe} if [ ! -x "$PROBE" ]; then bst_skip libiodbc/probe-runs "$PROBE absent from the image" exit 0 fi # The probe WRITES its three odbc.ini files, so it is handed a writable # directory. The deployed rootfs is read-only at browser runtime -- the same # constraint 77-unixodbc redirects ODBCSYSINI for -- so a probe pointed at its # own install directory would fail for a reason that has nothing to do with the # port. tmp=${TMPDIR:-/tmp}/bst-libiodbc.$$ mkdir -p "$tmp" 2>/dev/null pout=$tmp/probe.out "$PROBE" "$tmp" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok libiodbc/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun libiodbc/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" else bst_notok libiodbc/probe-runs "rc=$prc $(head -c 200 "$pout" 2>/dev/null)" fi if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libiodbc/probe-complete else bst_notok libiodbc/probe-complete "probe output has no PROBE-END terminator (last line: $(tail -1 "$pout" 2>/dev/null))" fi # val -- the probe's value for one case, empty when the probe stopped # first. Matched on the ID in field 1 rather than taken by position, since the # probe's output is captured with 2>&1. val() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$pout"; } # want -- score one probe value against a pinned answer. # A missing value is reported as the probe having stopped early rather than as a # mismatch against '', which is a different fault and localises to a different # line. want() { _got=$(val "$1") if [ -z "$_got" ]; then bst_notok "libiodbc/$2" "no value on target (probe stopped before $1); want='$3'" elif [ "$_got" = "$3" ]; then bst_ok "libiodbc/$2" else bst_notok "libiodbc/$2" "want:'$3' got:'$_got'" fi } # --- F: the widths the rest rests on ----------------------------------------- # These three are the only values that legitimately differ from the x86-64 # oracle, and they differ for the same reason 77-unixodbc records 4/4/2 rather # than a 64-bit host's 8/8/8. want F01 wordsize 32 # wasm32 is ILP32; the oracle host printed 64 # SQLINTEGER is `signed long` here: sqltypes.h picks `signed int` only under # `#if (SIZEOF_LONG == 8)`, so an ILP32 arm takes the other branch. 4 either way, # and the case is worth keeping because it pins which branch was compiled. want F02 sizeof-sqlinteger 4 # SQLLEN is `#define SQLLEN long` outside _WIN64 -- 8 on the LP64 oracle, 4 here. want F03 sizeof-sqllen 4 # The probe could write its three ini files. A failure here is the harness, not # the port, and every enumeration case below would be meaningless without it. want F04 ini-write-empty 0 want F05 ini-write-two 0 want F06 ini-write-driver 0 want F07 badmodule-write 0 # the not-a-shared-object payload group H loads want F08 ini-write-badmodule 0 # --- A: handles and the ODBC-3 version handshake ----------------------------- # 0 is SQL_SUCCESS. This is the sequence every ODBC application opens with. want A01 alloc-env 0 want A02 env-handle-nonnull 1 want A03 set-odbc-version 0 want A04 get-odbc-version-rc 0 # Read back rather than merely set: an attribute that silently did not take is # the failure this catches, and SQL_OV_ODBC3 is 3. want A05 odbc-version-readback 3 want A06 alloc-dbc 0 want A07 dbc-handle-nonnull 1 want A08 free-dbc 0 want A09 free-env 0 # --- B: enumeration against an ini that lists nothing ------------------------ # SQLDataSources is the one part of the manager that does real file I/O on the # target -- open/read/close through the guest's virtio-fs root. 100 is # SQL_NO_DATA. B is not redundant with C: a manager that never opened the file # would also report "no data", and only the PAIR distinguishes "read it and it # was empty" from "read nothing at all". want BCOUNT enum-empty-count 0 want BEND enum-empty-end 100 # --- C/U: enumeration against a two-entry ini -------------------------------- # Both names, both descriptions, in file order, then SQL_NO_DATA. The lengths # are scored too: SQLDataSources reports them through SQLSMALLINT out-params, # and a manager that returned the right strings with wrong lengths would break # every caller that trusts them. want C01NAME enum-two-name1 alpha want C01DESC enum-two-desc1 'Alpha Demo Driver' want C01LEN enum-two-len1 '5 17' want C02NAME enum-two-name2 beta want C02DESC enum-two-desc2 'Beta Demo Driver' want C02LEN enum-two-len2 '4 16' want CCOUNT enum-two-count 2 want CEND enum-two-end 100 # SQL_FETCH_FIRST_USER walks only the user ini, where SQL_FETCH_FIRST above # walks user then system. The two agree here because the probe points # SYSODBCINI at the empty file, which is what makes the C group's answer # attributable to the user ini alone. want U01NAME enum-user-name1 alpha want U02NAME enum-user-name2 beta want UCOUNT enum-user-count 2 want UEND enum-user-end 100 # --- D/E: the diagnostic contract on an unconfigured source ------------------ # The case the ticket names. -1 is SQL_ERROR. Each asserts the return AND the # SQLSTATE AND the text, because the ODBC contract is all three: a manager that # returned -1 with an empty diagnostic record would be broken in a way only the # second two catch, and one that returned success would be broken in a way an # application discovers much later. # # _iodbcdm_driverload pushes IM002 and returns BEFORE any dlopen, so this path # is pure manager logic and is fully scorable on a driverless arm. want DALLOC connect-unknown-alloc 0 want DRC connect-unknown-rc -1 want DDIAGRC connect-unknown-diagrc 0 want DSTATE connect-unknown-state IM002 want DMSG connect-unknown-text '[iODBC][Driver Manager]Data source name not found and no default driver specified. Driver could not be loaded' want DNATIVE connect-unknown-native 0 # SQLDriverConnect reaches the same refusal by a different route: a DSN= # keyword parsed out of a connection string rather than a DSN looked up in # odbc.ini. iODBC converges on IM002 for both, which is worth pinning because # unixODBC does NOT -- it answers 01000 for its DriverConnect equivalent # (77-unixodbc, driverconnect-unknown-state). The two managers genuinely differ # here and a reader comparing the suites should not read this as a copy. want EALLOC driverconnect-unknown-alloc 0 want ERC driverconnect-unknown-rc -1 want EDIAGRC driverconnect-unknown-diagrc 0 want ESTATE driverconnect-unknown-state IM002 want EMSG driverconnect-unknown-text '[iODBC][Driver Manager]Data source name not found and no default driver specified. Driver could not be loaded' # --- G: the driver-load path, and the one platform gap this port meets ------- # Here the source IS configured and names a driver file, so the manager gets # past the IM002 shortcut and reaches dlopen. On this arm that can never # succeed: no ODBC driver can be built at all while -shared does not link # (brintos.io/brintos/hwjs-cc/issues/59), the same wall 77-unixodbc documents # for libltdl. # # ONLY THE CONTRACT IS SCORED, AND THAT IS DELIBERATE. What must hold on any # platform is that the manager refuses, hands back a diagnostic record, and does # not crash or report success -- so GALLOC, GRC and GDIAGRC are pinned. The # SQLSTATE and the message text are NOT: the text is whatever the guest's dlopen # reported, which is a property of this arm's loader rather than of libiodbc, # and pinning today's string would be writing the test against the # implementation. They are printed as diagnostics instead, so a reader of the # transcript still sees exactly what the loader said. # # The native oracle answered rc=-1, diagrc=0, state=00000 and the message # "/usr/lib/libnosuchdriver.so: cannot open shared object file: No such file or # directory". Note the SUCCESS-class 00000 beside a real error: that is iODBC's # own behaviour on the dlopen failure path, not this arm's, and it is the second # reason not to pin the state -- a suite asserting IM003 here would be asserting # the ODBC standard against an implementation that does not follow it anywhere. want GALLOC connect-missing-module-alloc 0 want GRC connect-missing-module-rc -1 want GDIAGRC connect-missing-module-diagrc 0 # --- H: the same load against a driver file that DOES exist ------------------ # G alone cannot carry the finding. It names a path that is not there, so its # refusal is equally consistent with "no such file" and with "this arm cannot # load modules at all" -- and the second is the one that matters, because it is # what makes an ODBC driver impossible here rather than merely absent. H names a # file the probe staged itself: present, readable, and plain text, so it can # never be loadable for a reason of its own. # # This is the same pairing 77-unixodbc uses to establish libltdl's blanket # refusal, and it is worth repeating rather than citing because the two managers # reach the loader by different code and need not agree. # # The native x86-64 control DISTINGUISHES them -- "cannot open shared object # file: No such file or directory" for G against "invalid ELF header" for H -- # so a loader that inspects files produces two different answers here and one # that refuses everything produces the same answer twice. Both are meaningful # results, which is why neither text is pinned: what is scored is the contract # they share, and the two messages are printed side by side so the transcript # carries the comparison itself. # # MEASURED ON TARGET (2026-08-18), AND THE ANSWER IS THE FIRST ONE: this arm # distinguishes them, reporting errno accurately through its own loader -- # # G wasm32_user_dlopen_load("/usr/lib/libnosuchdriver.so") returned -2 ... # : No such file or directory (ENOENT) # H wasm32_user_dlopen_load(".../notashared.so") returned -8 ... # : Exec format error (ENOEXEC) # # So the refusal here is NOT the blanket one 77-unixodbc records, and the two # findings do not conflict: that suite measured libltdl, whose tryall_dlopen # collapses every failure onto LT_ERROR_FILE_NOT_FOUND before a caller sees it, # which is a property of libltdl rather than of this arm. iODBC calls dlopen # directly and the accurate errno survives. # # The wall is therefore not "no module can be loaded" but "no module can be # BUILT" -- brintos.io/brintos/hwjs-cc/issues/59, -shared does not link -- and # these two lines are what locates it there. Left as diagnostics rather than # pinned all the same: the wording is the guest kernel's staging message and is # its to change, and a suite asserting it would go red on an improvement. want HALLOC connect-existing-module-alloc 0 want HRC connect-existing-module-rc -1 want HDIAGRC connect-existing-module-diagrc 0 # A diagnostic record that came back EMPTY would satisfy every case above -- # GDIAGRC/HDIAGRC only say a record was returned, not that it said anything -- # and an application would then report a failure it cannot explain. Both # messages must name the driver path they refused. for g in G H; do _m=$(val "${g}MSG") case "$_m" in *'[iODBC][Driver Manager]'*'.so'*) bst_ok "libiodbc/diag-text-$g" ;; '') bst_notok "libiodbc/diag-text-$g" "the manager refused with an EMPTY diagnostic message" ;; *) bst_notok "libiodbc/diag-text-$g" "diagnostic does not name the refused driver: '$_m'" ;; esac done printf '#diag: G (module absent) state=%s\n' "$(val GSTATE)" printf '#diag: G (module absent) msg=%s\n' "$(val GMSG)" printf '#diag: H (module present) state=%s\n' "$(val HSTATE)" printf '#diag: H (module present) msg=%s\n' "$(val HMSG)" rm -rf "$tmp" 2>/dev/null