# unixODBC 2.3.12 — the ODBC driver manager (BRINTOS-976). # # The package has two halves and they are tested as a pair, because each is only # half a port on its own: odbcinst(1) WRITES the ini files, and the Driver # Manager library READS them. A suite that drove only the tool would prove the # files were edited by something nothing consumes; one that drove only the # library would prove it read files nothing this image can write. So the shape # here is: point both at one temporary ini directory, have the tool install a # driver and a DSN into it, and have unixodbc-probe — cross-linked against the # shipped /usr/lib/libodbc.a — find that same driver and DSN through # SQLDrivers/SQLDataSources. # # NO ODBC DRIVER IS PORTED TO THIS ARM, and the suite is built around that # rather than in spite of it. isql is interactive and is not the acceptance; # 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. Three refusals are asserted, and they are three different code paths: # SQLConnect on a DSN in no ini file (IM002), SQLDriverConnect on a DRIVER= name # in no driver table, and SQLConnect on a DSN that resolves all the way to a # module path. Only the last reaches libltdl, and the diagnostic text is # asserted alongside every SQLSTATE because an rc alone would pass for a manager # that failed for an unrelated reason. # # The last two answer 01000 and not the ODBC-specified IM003. That was measured, # not assumed, and it is unixODBC's own behaviour rather than this arm's — # SQLConnect.c posts ERROR_01000 with "Can't open lib '%s' : %s" whenever # odbc_dlopen fails, on every platform. # # A FOURTH case exists because the first three do not settle the platform # question: the stub module path does not exist, so a refusal there is equally # consistent with "no such file" and with "this arm cannot load modules". The # bad-module entry names a file that IS present and is not a shared object, and # gets the same answer — which is what makes the finding a blanket refusal # rather than a missing-file artifact. See that case for the detail. # # ODBCSYSINI and ODBCINI are exported for the whole file. That is not just # hygiene: the deployed rootfs is READ-ONLY at browser runtime, so the shipped # /etc/odbcinst.ini cannot be edited in place and an odbcinst that wrote to the # default path would fail for the wrong reason. Redirecting both to a tmpfs # directory is also what lets the install/query/remove cycle assert a state it # created itself, rather than whatever the image happened to ship. # # The two width cases are worth naming. odbc_config --ulen is what a consumer's # build reads to pin its SQLULEN width, and this arm cannot produce that value # at BUILD time — unixODBC generates it by RUNNING the freshly cross-built # odbc_config, which no wasm32 build host can do, so odbc.pc ships with an empty # ulen= field. On target the binary runs fine, so the value is established here # instead, and asserted from both sides: odbcinst -j and the probe's own # sizeof(). The widths are 4/4/2 here, not the 8/8/8 of every 64-bit host. . "$BST_LIB" require odbcinst unixodbc/present bst_ok unixodbc/present # --- the package is whole ---------------------------------------------------- # All six exe/ 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 odbc_config isql iusql dltest slencheck; do if command -v "$t" >/dev/null 2>&1; then bst_ok "unixodbc/$t-present" else bst_notok "unixodbc/$t-present" "$t missing though odbcinst is installed"; fi done check unixodbc/header-sql test -s /usr/include/sql.h check unixodbc/header-odbcinst test -s /usr/include/odbcinst.h check unixodbc/staticlib test -s /usr/lib/libodbc.a check unixodbc/staticlib-inst test -s /usr/lib/libodbcinst.a check unixodbc/shipped-ini test -f /etc/odbcinst.ini # A real ar archive carrying the driver-manager entry points. The -staticdev # package is a link target, so an archive that is present but truncated would # pass the size check above and fail every consumer. check_eq unixodbc/staticlib-ar '!' "$(head -c 7 /usr/lib/libodbc.a)" if grep -q SQLDriverConnect /usr/lib/libodbc.a 2>/dev/null; then bst_ok unixodbc/staticlib-symbols else bst_notok unixodbc/staticlib-symbols "libodbc.a carries no SQLDriverConnect" fi # --- redirect both ini paths into a writable directory ----------------------- tmp=${TMPDIR:-/tmp}/bst-unixodbc.$$ rm -rf "$tmp"; mkdir -p "$tmp" ODBCSYSINI=$tmp; export ODBCSYSINI ODBCINI=$tmp/user-odbc.ini; export ODBCINI # --- odbcinst -j: where the library thinks its files are --------------------- # -j is the one subcommand that reports the library's own view of its # configuration, so it is what says the two environment variables took. Each # path is asserted separately: a single check on $tmp would pass if only one of # the four lines had been redirected. j=$(odbcinst -j 2>&1); jrc=$? check_eq unixodbc/j-rc 0 "$jrc" case "$j" in *"unixODBC 2.3.12"*) bst_ok unixodbc/j-version ;; *) bst_notok unixodbc/j-version "$(_bst_1line "$j")" ;; esac case "$j" in *"DRIVERS............: $tmp/odbcinst.ini"*) bst_ok unixodbc/j-drivers-path ;; *) bst_notok unixodbc/j-drivers-path "ODBCSYSINI not honoured: $(_bst_1line "$j")" ;; esac case "$j" in *"SYSTEM DATA SOURCES: $tmp/odbc.ini"*) bst_ok unixodbc/j-system-dsn-path ;; *) bst_notok unixodbc/j-system-dsn-path "$(_bst_1line "$j")" ;; esac case "$j" in *"USER DATA SOURCES..: $ODBCINI"*) bst_ok unixodbc/j-user-dsn-path ;; *) bst_notok unixodbc/j-user-dsn-path "ODBCINI not honoured: $(_bst_1line "$j")" ;; esac # The driver-ABI widths. 4/4/2 is not an arbitrary triple: sqltypes.h branches on # SIZEOF_LONG_INT, and wasm32 is ILP32, so it takes the 32-bit arm where # SQLLEN/SQLULEN are SQLINTEGER/SQLUINTEGER (4) and SQLSETPOSIROW falls back to # SQLUSMALLINT (2) — the narrow ODBC-2 shape, unlike every 64-bit host where all # three are 8. A driver built against different widths than the manager is the # classic unixODBC ABI break, which is why these are asserted at all. check_out unixodbc/j-sqlulen 'SQLULEN Size.......: 4' odbcinst -j check_out unixodbc/j-sqllen 'SQLLEN Size........: 4' odbcinst -j check_out unixodbc/j-sqlsetposirow 'SQLSETPOSIROW Size.: 2' odbcinst -j # --- the empty state, so the installs below are provably ours ---------------- # unixODBC reports "no such section" through the installer-error channel rather # than as empty output, so this exact line IS the empty answer — asserting '' # would be asserting a behaviour the tool does not have. It is asserted here and # again after the removals, which is what makes the pair meaningful. NOSECTION='odbcinst: SQLGetPrivateProfileString failed with .' check_eq unixodbc/query-drivers-empty "$NOSECTION" "$(odbcinst -q -d 2>&1)" check_eq unixodbc/query-dsn-empty "$NOSECTION" "$(odbcinst -q -s 2>&1)" # --- install a driver entry from the shipped template ------------------------ inst=$(odbcinst -i -d -f "$BST_DATA/unixodbc-driver.tmpl" 2>&1); inst_rc=$? check_eq unixodbc/install-driver-rc 0 "$inst_rc" case "$inst" in *"Driver installed. Usage count increased to 1"*) bst_ok unixodbc/install-driver-msg ;; *) bst_notok unixodbc/install-driver-msg "$(_bst_1line "$inst")" ;; esac check unixodbc/install-driver-file test -s "$tmp/odbcinst.ini" check_out unixodbc/query-driver-listed '[BST Stub Driver]' odbcinst -q -d # The attributes come back as well as the name — a section header alone would # pass for an entry whose body was dropped. check_out unixodbc/query-driver-attrs '/usr/lib/libbststubodbc.so' \ odbcinst -q -d -n 'BST Stub Driver' # --- a system DSN needs its ini file to exist first --------------------------- # This is the asymmetry between the two halves of the tool, and it is asserted # rather than worked around because it is what an application on this target # will hit. A DRIVER install goes through _SQLWriteInstalledDrivers, which # CREATES odbcinst.ini — which is why the section above needed no setup. A DSN # install goes through SQLWritePrivateProfileString -> _odbcinst_ConfigModeINI # -> _odbcinst_SystemINI(..., bVerify=TRUE), which fopen()s the target "r" and # returns FALSE if it is not already there. unixODBC never creates odbc.ini, and # neither upstream's `make install` nor meta-oe's recipe ships one, so the image # has /etc/odbcinst.ini and no /etc/odbc.ini at all. predsn=$(odbcinst -i -s -l -f "$BST_DATA/unixodbc-dsn.tmpl" 2>&1); predsn_rc=$? check_eq unixodbc/install-dsn-needs-file-rc 1 "$predsn_rc" case "$predsn" in *'SQLWritePrivateProfileString failed'*) bst_ok unixodbc/install-dsn-needs-file-msg ;; *) bst_notok unixodbc/install-dsn-needs-file-msg "$(_bst_1line "$predsn")" ;; esac # --- install a system DSN that names that driver ----------------------------- : > "$tmp/odbc.ini" dsn=$(odbcinst -i -s -l -f "$BST_DATA/unixodbc-dsn.tmpl" 2>&1); dsn_rc=$? check_eq unixodbc/install-dsn-rc 0 "$dsn_rc" # A successful -i -s -f says NOTHING, and that is asserted rather than papered # over. odbcinst.c guards its confirmation with `if ( cVerbose == 0 && from_stdin # ) ... else if ( cVerbose )`, which leaves the default path -- no -v, template # from a file -- matching neither branch. So silence on success is the contract # this tool actually has, and a future version that started printing would be a # change worth noticing. check_eq unixodbc/install-dsn-silent '' "$(_bst_1line "$dsn")" check unixodbc/install-dsn-file test -s "$tmp/odbc.ini" check_out unixodbc/query-dsn-listed '[BSTStubDSN]' odbcinst -q -s check_out unixodbc/query-dsn-attrs 'BST Stub Driver' odbcinst -q -s -n BSTStubDSN # --- the library's side: does it read what the tool just wrote? -------------- # The payload ships 0644 on a read-only rootfs, so it is staged into the tmpfs # and made executable there. probe=$tmp/unixodbc-probe check unixodbc/probe-shipped test -f "$BST_DATA/unixodbc-probe" if cp "$BST_DATA/unixodbc-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then out=$("$probe" BSTStubDSN 2>&1); prc=$? bst_ok unixodbc/probe-stage else out=''; prc=127 bst_notok unixodbc/probe-stage "cannot stage $BST_DATA/unixodbc-probe" fi # Handle allocation and release are the probe's own contract; its exit status is # the count of those that failed, so 0 here means the API skeleton is sound # independently of any connect. check_eq unixodbc/probe-rc 0 "$prc" check_eq unixodbc/probe-alloc-env 'ok' "$(printf '%s\n' "$out" | awk '/^UO-alloc-env /{print $3}')" check_eq unixodbc/probe-setver 'ok' "$(printf '%s\n' "$out" | awk '/^UO-setver-odbc3 /{print $3}')" check_eq unixodbc/probe-alloc-dbc 'ok' "$(printf '%s\n' "$out" | awk '/^UO-alloc-dbc /{print $3}')" check_eq unixodbc/probe-free-dbc 'ok' "$(printf '%s\n' "$out" | awk '/^UO-free-dbc /{print $3}')" check_eq unixodbc/probe-free-env 'ok' "$(printf '%s\n' "$out" | awk '/^UO-free-env /{print $3}')" # The widths, from the library's side this time. They must agree with the three # odbcinst -j cases above; asserting both is what makes a disagreement visible # rather than a shared wrong answer. check_eq unixodbc/probe-sqlulen 4 "$(printf '%s\n' "$out" | awk '/^UO-sizeof-sqlulen /{print $2}')" check_eq unixodbc/probe-sqllen 4 "$(printf '%s\n' "$out" | awk '/^UO-sizeof-sqllen /{print $2}')" check_eq unixodbc/probe-sqlsetposirow 2 \ "$(printf '%s\n' "$out" | awk '/^UO-sizeof-sqlsetposirow /{print $2}')" # SQLDrivers and SQLDataSources enumerate the very entries odbcinst wrote. This # is the join between the two halves of the package. check_eq unixodbc/probe-drivers-count 1 \ "$(printf '%s\n' "$out" | sed -n 's/^UO-drivers .* n=\([0-9]*\)$/\1/p')" case "$out" in *'UO-drivers names=BST Stub Driver'*) bst_ok unixodbc/probe-drivers-name ;; *) bst_notok unixodbc/probe-drivers-name \ "$(_bst_1line "$(printf '%s\n' "$out" | grep '^UO-drivers ')")" ;; esac check_eq unixodbc/probe-datasources-count 1 \ "$(printf '%s\n' "$out" | sed -n 's/^UO-datasources .* n=\([0-9]*\)$/\1/p')" case "$out" in *'UO-datasources names=BSTStubDSN'*) bst_ok unixodbc/probe-datasources-name ;; *) bst_notok unixodbc/probe-datasources-name \ "$(_bst_1line "$(printf '%s\n' "$out" | grep '^UO-datasources ')")" ;; esac # --- the three refusals ------------------------------------------------------ # SQL_ERROR is -1. Each case asserts the return AND the SQLSTATE AND the text, # because the ODBC contract is all three and a manager that returned -1 with an # empty diagnostic record would be broken in a way only the second two catch. # # The SQLSTATE is matched against the whole diagnostic LIST rather than against # record 1: ODBC returns several records and the position of the interesting one # is the driver manager's business — SQLDriverConnect files a 01000 general # warning first. Asserting record 1 would pin an implementation detail; asserting # membership pins the contract. diag_states() { printf '%s\n' "$out" \ | sed -n "s/^UO-$1-diag n=[0-9]* rc=[-0-9]* state=\([A-Za-z0-9]*\) .*/\1/p" \ | tr '\n' ' ' } want_state() { # want_state case " $(diag_states "$2") " in *" $3 "*) bst_ok "$1" ;; *) bst_notok "$1" "want $3 among [$(diag_states "$2")]" ;; esac } check_eq unixodbc/connect-unknown-rc -1 \ "$(printf '%s\n' "$out" | sed -n 's/^UO-connect-unknown rc=\(-*[0-9]*\)$/\1/p')" want_state unixodbc/connect-unknown-state connect-unknown IM002 case "$out" in *'Data source name not found'*) bst_ok unixodbc/connect-unknown-text ;; *) bst_notok unixodbc/connect-unknown-text \ "$(_bst_1line "$(printf '%s\n' "$out" | grep '^UO-connect-unknown-diag ')")" ;; esac # SQLDriverConnect takes a different route: DRIVER= skips the DSN table, and # when the name is in no driver table either, unixODBC does not answer IM002 — # it hands the NAME ITSELF to the loader and reports what came back. Asserting # the name inside the message is what pins that, and it is upstream's behaviour # rather than this arm's. check_eq unixodbc/driverconnect-unknown-rc -1 \ "$(printf '%s\n' "$out" | sed -n 's/^UO-driverconnect-unknown rc=\(-*[0-9]*\)$/\1/p')" want_state unixodbc/driverconnect-unknown-state driverconnect-unknown 01000 case "$out" in *"Can't open lib 'BST No Such Driver'"*) bst_ok unixodbc/driverconnect-unknown-text ;; *) bst_notok unixodbc/driverconnect-unknown-text \ "$(_bst_1line "$(printf '%s\n' "$out" | grep '^UO-driverconnect-unknown-diag ')")" ;; esac # The registered-but-unloadable case: the DSN resolves through the driver table # to a module path, and the module does not load. Reaching it at all means the # whole chain ran — this is the join the two halves of the package exist for. # # 01000, not the ODBC-specified IM003, and that is unixODBC's own choice rather # than a platform deviation: SQLConnect.c's odbc_dlopen failure path calls # __post_internal_error(..., ERROR_01000, "Can't open lib '%s' : %s", ...). A # suite asserting IM003 here would be asserting the standard against an # implementation that does not follow it on any platform. check_eq unixodbc/connect-stub-rc -1 \ "$(printf '%s\n' "$out" | sed -n 's/^UO-connect-stub rc=\(-*[0-9]*\) .*/\1/p')" want_state unixodbc/connect-stub-state connect-stub 01000 case "$out" in *"Can't open lib '/usr/lib/libbststubodbc.so' : file not found"*) bst_ok unixodbc/connect-stub-text ;; *) bst_notok unixodbc/connect-stub-text \ "$(_bst_1line "$(printf '%s\n' "$out" | grep '^UO-connect-stub-diag ')")" ;; esac # --- the same refusal against a module path that DOES exist ------------------ # The stub entry above names a path that is not there, so on its own it cannot # say whether the refusal came from the file being missing or from this arm # having no dynamic loading at all. This entry names a file that IS present and # readable and is not a shared object. # # Measured, both answer identically: "Can't open lib '' : file not found". # That string is libltdl's CATCH-ALL (LT_ERROR_FILE_NOT_FOUND, which tryall_dlopen # reports whenever no loader would take the file) and not a stat result, so the # pair establishes that the refusal is blanket rather than an artifact of the # missing path — and that libltdl gives a caller here no way to tell the two # apart. That is the expected end state on a target where -shared does not link # at all (brintos.io/brintos/hwjs-cc/issues/59), so no ODBC driver can exist to # load; it is the reason this suite is driver-free, and it is asserted rather # than assumed. odbcinst -i -d -f "$BST_DATA/unixodbc-driver-badmodule.tmpl" >/dev/null 2>&1 check_eq unixodbc/install-badmodule-rc 0 "$?" odbcinst -i -s -l -f "$BST_DATA/unixodbc-dsn-badmodule.tmpl" >/dev/null 2>&1 check_out unixodbc/query-badmodule-listed '[BST Bad Module]' odbcinst -q -d bad=$("$probe" BSTBadModuleDSN 2>&1) check_eq unixodbc/connect-badmodule-rc -1 \ "$(printf '%s\n' "$bad" | sed -n 's/^UO-connect-stub rc=\(-*[0-9]*\) .*/\1/p')" # The refusal must name the file it was pointed at — that is what says the DSN # resolved and the loader was handed a real path rather than declining earlier. case "$bad" in *"Can't open lib '/usr/bin/odbcinst'"*) bst_ok unixodbc/connect-badmodule-names-file ;; *) bst_notok unixodbc/connect-badmodule-names-file \ "$(_bst_1line "$(printf '%s\n' "$bad" | grep '^UO-connect-stub-diag ')")" ;; esac # It must refuse rather than crash, hang or claim success, and it must SAY # something: an empty loader error would mean the failure went unreported, which # is the one outcome a caller could not act on. badmsg=$(printf '%s\n' "$bad" | sed -n "s/^UO-connect-stub-diag n=1 .*Can't open lib '\/usr\/bin\/odbcinst' : //p") if [ -n "$badmsg" ] && [ "$badmsg" != "NULL ERROR RETURN" ]; then bst_ok unixodbc/connect-badmodule-reports else bst_notok unixodbc/connect-badmodule-reports "loader gave no error text: [$badmsg]" fi # --- remove every entry and prove the files went back ------------------------ # The bad-module pair goes first and unasserted: it exists only to have reached # the loader above, and the count assertions at the end of this section are the # ones that would catch it being left behind. odbcinst -u -s -l -n BSTBadModuleDSN >/dev/null 2>&1 odbcinst -u -d -n 'BST Bad Module' >/dev/null 2>&1 undsn=$(odbcinst -u -s -l -n BSTStubDSN 2>&1); undsn_rc=$? check_eq unixodbc/remove-dsn-rc 0 "$undsn_rc" case "$undsn" in *'DSN removed (if it existed at all)'*) bst_ok unixodbc/remove-dsn-msg ;; *) bst_notok unixodbc/remove-dsn-msg "$(_bst_1line "$undsn")" ;; esac # "Gone" is asserted as the absence of the NAME rather than as an exact output: # the ini file still exists here, unlike in the empty-state cases above, and # whether odbcinst then answers with nothing or with its no-section line is not # what the removal claims. case "$(odbcinst -q -s 2>&1)" in *BSTStubDSN*) bst_notok unixodbc/query-dsn-gone "BSTStubDSN still listed after -u -s" ;; *) bst_ok unixodbc/query-dsn-gone ;; esac undrv=$(odbcinst -u -d -n 'BST Stub Driver' 2>&1); undrv_rc=$? check_eq unixodbc/remove-driver-rc 0 "$undrv_rc" case "$undrv" in *'usage count became zero'*) bst_ok unixodbc/remove-driver-msg ;; *) bst_notok unixodbc/remove-driver-msg "$(_bst_1line "$undrv")" ;; esac case "$(odbcinst -q -d 2>&1)" in *'BST Stub Driver'*) bst_notok unixodbc/query-driver-gone "BST Stub Driver still listed after -u -d" ;; *) bst_ok unixodbc/query-driver-gone ;; esac # The library must agree that both are gone — the removals are only real if the # side that reads the files sees them go. out2=$("$probe" 2>&1) check_eq unixodbc/probe-drivers-gone 0 \ "$(printf '%s\n' "$out2" | sed -n 's/^UO-drivers .* n=\([0-9]*\)$/\1/p')" check_eq unixodbc/probe-datasources-gone 0 \ "$(printf '%s\n' "$out2" | sed -n 's/^UO-datasources .* n=\([0-9]*\)$/\1/p')" # --- odbc_config: the value a consumer's build reads ------------------------- # --odbcini reports the COMPILED-IN path and is deliberately not env-sensitive: # odbc_config answers questions about how the package was built, which is what a # consumer's configure script needs, while ODBCSYSINI/ODBCINI steer where the # library looks at RUN time. Asserting /etc/odbc.ini here rather than $tmp is # therefore the assertion, not a concession — and it is the same value that is # missing from odbc.pc's ulen= field, because that one is generated by RUNNING # this binary at build time, which no wasm32 build host can do. check_out unixodbc/odbc_config-version '2.3.12' odbc_config --version check_out unixodbc/odbc_config-ulen '-DSIZEOF_SQLULEN=4' odbc_config --ulen check_out unixodbc/odbc_config-odbcini '/etc/odbc.ini' odbc_config --odbcini rm -rf "$tmp"