. "$BST_LIB" # BST-TIMEOUT: 600 # two wasm32 spawns of a 3 MB probe + an archive-wide grep # gpgme (GnuPG Made Easy) is the high-level API applications use to talk to # GnuPG -- how a mail client signs, verifies, encrypts and looks up keys without # shelling out to gpg and parsing its output. It sits on libgpg-error (error # codes) and libassuan (the IPC it speaks to the engine), both already in this # image, and carries no crypto of its own: every cryptographic operation is # performed by an ENGINE, a gpg/gpgsm binary gpgme spawns and drives over Assuan. # # That split is the whole shape of this suite, and of the port. # # --------------------------------------------------------------------------- # THE HALF THAT NEEDS NO ENGINE, AND THE HALF THAT DOES # # Most of the public API never starts a process: version and error mapping, # contexts and their attributes, the data-object layer (memory, file and # callback-backed), the algorithm and protocol name tables, signature notations, # and gpgme_data_identify -- which parses PGP armor and packet headers # in-process, so it tells a key from a message with no gpg at all. The probe's # --core mode is exactly that half: 76 cases, and they run here in full. # # The line between the halves is drawn by MEASUREMENT, not by reading the API. # Five cases were in --core until the first target run put them where they # belong: gpgme_get_dirinfo("homedir") and ("gpg-name") answer out of gpgconf's # output, the OpenPGP engine-info entry only carries a file_name once an engine # is found, and gpgme_ctx_set_engine_info COPIES the global entry for the # protocol -- so with no engine there is nothing to copy and the override fails. # All four are correct behaviour on an image with no gpg; asserting them in # --core would have been asserting that gpg is present. # # The other half -- generate a key, sign a buffer, verify it back, list the # keyring -- cannot run without a gpg binary, and THIS IMAGE HAS NONE YET: the # gnupg port is BRINTOS-716, in the same Epic, not landed at the time of writing. # So the probe's --engine mode reports every one of its cases as `skip`, naming # that, rather than as a pass nobody proved or a failure gpgme did not cause. The # case names stay in the report either way, so the day gnupg lands this suite # turns green by itself with no edit here -- which is why the skips are computed # from gpgme_engine_check_version() at runtime and not hard-coded. # # The engine half IS proven, on x86_64, against the same gnupg 2.4.9 the sibling # ticket is porting: 21/21 including createkey in a scratch GNUPGHOME, a NORMAL-mode # signature whose fingerprint matches the generated key, a verify that recovers # the plaintext byte for byte, a flipped byte that is refused, and a clearsigned # armored output. What the wasm32 arm has not yet run is the engine SPAWN, not # the API. # # --------------------------------------------------------------------------- # WHY A PROBE AND NOT PAYLOAD SHAPE ALONE # # The distro is static-only, so this port installs nothing a shell can usefully # drive: gpgme-config is poky's binconfig-disabled refusal stub, gpgme-tool is # not built at all (its result_xml_indent() is a VLA-address-escape the # coroutinize pass refuses), and gpgme-json -- which does ship -- is a JSON-RPC # server for an engine this image has not got. What the port delivers is # libgpgme.a, the header, the pkg-config file and the aclocal macro. "The files # are present" is therefore the whole of what a shell can say about it -- and # BRINTOS-217 is why that is not enough on this arm: oniguruma compiled and # packaged perfectly and then returned silently wrong offsets to every caller. A # library whose failure mode is answering WRONGLY needs something that reads the # answers. # # So data/gpgme-probe is src/gpgme_probe.c cross-linked with wasmcc-glibc against # THE VERY ARCHIVE THAT SHIPS (/usr/lib/libgpgme.a) and the header beside it, # staged to $TMPDIR and run here. Its --core transcript is compared byte for byte # against data/gpgme-native.txt, the identical sources built and run on x86_64 # (src/gpgme_mknative.sh regenerates it from the same three tarballs the image # builds: libgpg-error 1.48, libassuan 2.5.6, gpgme 1.23.2). That comparison is # the case that catches a target binary whose checks stop RUNNING rather than # fail -- the failure mode a per-case assertion cannot see. # # The probe runs ONCE per mode and its output is read into a shell variable; every # assertion below is a `case` glob against that variable, so there is no fork per # case. On this runtime the forking is the cost, not the work. # --------------------------------------------------------------------------- incd=/usr/include lib=/usr/lib/libgpgme.a pc=/usr/lib/pkgconfig/gpgme.pc nl=' ' hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } nofile() { if [ -e "$2" ]; then bst_notok "$1" "present, and should not be: $2"; else bst_ok "$1"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not in archive: $2" ;; esac; } # --- the shipped payload ----------------------------------------------------- hasfile gpgme/header "$incd/gpgme.h" hasfile gpgme/archive "$lib" hasfile gpgme/pkgconfig "$pc" # The C++ binding is deliberately NOT built (LANGUAGES = "" in the bbappend): # libgpgmepp needs a C++ standard library this toolchain does not have yet # (hwjs-cc#10). These two cases hold that decision in place -- if a recipe bump # quietly re-enables it, the link would be the thing that failed, not this. nofile gpgme/no-cpp-archive "/usr/lib/libgpgmepp.a" nofile gpgme/no-cpp-header "$incd/gpgme++" pctext=$(cat "$pc" 2>/dev/null) has gpgme/pc-name "Name: gpgme" "$pctext" has gpgme/pc-version "Version: 1.23.2" "$pctext" has gpgme/pc-libs "-lgpgme" "$pctext" # gpgme cannot work without its two dependencies, and on a static-only distro # nothing pulls them in implicitly: the .pc file IS the link line a consumer gets. has gpgme/pc-requires-assuan "libassuan" "$pctext" has gpgme/pc-requires-gpgerror "gpg-error" "$pctext" # WHAT `#include ` ACTUALLY RESOLVES TO, which is not the header the # package built. do_install runs oe_multilib_header, so /usr/include/gpgme.h is a # 508-byte dispatcher that reads bits/wordsize.h and includes gpgme-32.h or # gpgme-64.h; the 96 kB header gpgme generated is at gpgme-32.h. On a 32-bit-only # distro with no multilib the indirection is inert -- but it is still the path a # consumer takes, so both halves are asserted rather than assumed, and every # content check below reads the REAL header. real_hdr=$incd/gpgme-32.h hasfile gpgme/header-multilib-real "$real_hdr" wraptext=$(cat "$incd/gpgme.h" 2>/dev/null) has gpgme/header-multilib-dispatch "#include " "$wraptext" has gpgme/header-multilib-wordsize "__WORDSIZE == 32" "$wraptext" # gpgme-32.h is generated at configure time from gpgme.h.in, so these lines are # what the cross-configure concluded about this arm rather than upstream defaults. hdrtext=$(cat "$real_hdr" 2>/dev/null) has gpgme/hdr-version "#define GPGME_VERSION \"1.23.2\"" "$hdrtext" has gpgme/hdr-version-num "#define GPGME_VERSION_NUMBER 0x011702" "$hdrtext" # gpgme.h.in stamps the triple configure was given into the generated header. A # header carrying some other arm's triple means a sysroot mix-up, which no file # test can see and which every other case here would sail past. has gpgme/hdr-triple "for wasm32-hwjs-linux-gnu" "$hdrtext" # The public gpgme_off_t: upstream picks off64_t on a large-file host, and this # arm's recipe carries a patch to keep it off_t. Pinned because it is an ABI # promise to every consumer, decided at configure time, and invisible otherwise. has gpgme/hdr-off-t "typedef off_t gpgme_off_t;" "$hdrtext" # --- symbol completeness, derived from the INSTALLED header ------------------ # Every gpgme_* function the installed header declares must be a symbol in the # installed archive: a partial compile, a dropped source file or a header/library # skew all fail this, and none of them would show up as a missing file. arcsyms=$(grep -ao 'gpgme_[a-z0-9_]*' "$lib" 2>/dev/null | sort -u) hdrsyms=$(grep -vE '^[[:space:]]*#' "$real_hdr" 2>/dev/null \ | grep -oE '\bgpgme_[a-z0-9_]*[[:space:]]*\(' \ | sed 's/[[:space:]]*(//' | sort -u) nsym=0 nmiss=0 missing="" for f in $hdrsyms; do # _t names are types, not entry points; the header declares them the same way # a call site spells a function, so drop them rather than chase them. # # Five names are excluded BY CONSTRUCTION, not to make this pass: each is a # `static _GPGME_INLINE` wrapper defined in the header and compiled into the # CALLER, so no archive on any arm can carry it. # # The list is the header's, not an arm's. Which of the five happen to survive # as ASCII in an archive differs by object format -- the native x86_64 control # shows two of them and this arm's bitcode archive shows a different three -- # so a list tuned to either arm's leftovers would go red on the other for a # reason that has nothing to do with gpgme. `grep -A2 '^static _GPGME_INLINE' # gpgme-32.h` regenerates it if upstream adds one. case "$f" in *_t|gpgme_err_code|gpgme_err_make|gpgme_error|gpgme_error_from_syserror|gpgme_err_source) continue ;; esac nsym=$((nsym + 1)) case "$nl$arcsyms$nl" in *"$nl$f$nl"*) ;; *) nmiss=$((nmiss + 1)); missing="$missing $f" ;; esac done if [ "$nmiss" -eq 0 ]; then bst_ok gpgme/symbols-complete else bst_notok gpgme/symbols-complete "$nmiss missing:$missing" fi # ...and the count, so a derivation that read nothing cannot pass the loop above # vacuously -- the failure mode this guard exists for is `real_hdr` pointing at # the 508-byte multilib wrapper, which yields an empty list and a green loop. check_eq gpgme/symbols-count 210 "$nsym" # A spot check that the derivation above is reading the archive rather than an # empty string, which would make the loop vacuously green. hasline gpgme/sym-op-sign gpgme_op_sign "$arcsyms" hasline gpgme/sym-op-verify gpgme_op_verify "$arcsyms" hasline gpgme/sym-op-createkey gpgme_op_createkey "$arcsyms" hasline gpgme/sym-op-keylist-next gpgme_op_keylist_next "$arcsyms" hasline gpgme/sym-data-identify gpgme_data_identify "$arcsyms" hasline gpgme/sym-new gpgme_new "$arcsyms" # --- the library RUNNING, via the cross-linked probe ------------------------- probe=${TMPDIR:-/tmp}/bst-gpgme-probe.$$ if ! cp "$BST_DATA/gpgme-probe" "$probe" 2>/dev/null || ! chmod +x "$probe" 2>/dev/null; then bst_notok gpgme/probe-shipped "cannot stage $BST_DATA/gpgme-probe" rm -f "$probe" return 0 2>/dev/null || exit 0 fi bst_ok gpgme/probe-shipped core=$("$probe" --core 2>&1) # version, and the error mapping every other call reports through has gpgme/version-check "gpgme/version-check ok" "$core" has gpgme/version-matches-header "gpgme/version-matches-header ok" "$core" has gpgme/version-required-ok "gpgme/version-required-ok ok" "$core" has gpgme/version-required-refused "gpgme/version-required-refused ok" "$core" has gpgme/err-code-roundtrip "gpgme/err-code-roundtrip ok" "$core" has gpgme/err-source "gpgme/err-source ok" "$core" has gpgme/err-make-source "gpgme/err-make-source ok" "$core" has gpgme/err-strsource "gpgme/err-strsource ok" "$core" has gpgme/err-strerror "gpgme/err-strerror ok" "$core" has gpgme/err-strerror-r "gpgme/err-strerror-r ok" "$core" has gpgme/err-from-errno "gpgme/err-from-errno ok" "$core" has gpgme/err-to-errno "gpgme/err-to-errno ok" "$core" has gpgme/err-from-syserror "gpgme/err-from-syserror ok" "$core" # the name tables -- pure lookups, but they are what a caller prints to a user has gpgme/protocol-name-openpgp "gpgme/protocol-name-openpgp ok" "$core" has gpgme/protocol-name-cms "gpgme/protocol-name-cms ok" "$core" has gpgme/protocol-name-bogus "gpgme/protocol-name-bogus ok" "$core" has gpgme/pubkey-algo-rsa "gpgme/pubkey-algo-rsa ok" "$core" has gpgme/pubkey-algo-dsa "gpgme/pubkey-algo-dsa ok" "$core" has gpgme/pubkey-algo-ecdsa "gpgme/pubkey-algo-ecdsa ok" "$core" has gpgme/pubkey-algo-eddsa "gpgme/pubkey-algo-eddsa ok" "$core" has gpgme/hash-algo-sha1 "gpgme/hash-algo-sha1 ok" "$core" has gpgme/hash-algo-sha256 "gpgme/hash-algo-sha256 ok" "$core" has gpgme/hash-algo-sha512 "gpgme/hash-algo-sha512 ok" "$core" has gpgme/addrspec-from-uid "gpgme/addrspec-from-uid ok" "$core" has gpgme/addrspec-bare "gpgme/addrspec-bare ok" "$core" has gpgme/dirinfo-bogus "gpgme/dirinfo-bogus ok" "$core" # contexts and every attribute a caller sets before an operation has gpgme/ctx-new "gpgme/ctx-new ok" "$core" has gpgme/ctx-protocol-default "gpgme/ctx-protocol-default ok" "$core" has gpgme/ctx-set-protocol "gpgme/ctx-set-protocol ok" "$core" has gpgme/ctx-set-protocol-bogus "gpgme/ctx-set-protocol-bogus ok" "$core" has gpgme/ctx-armor-on "gpgme/ctx-armor-on ok" "$core" has gpgme/ctx-armor-off "gpgme/ctx-armor-off ok" "$core" has gpgme/ctx-textmode "gpgme/ctx-textmode ok" "$core" has gpgme/ctx-offline "gpgme/ctx-offline ok" "$core" has gpgme/ctx-keylist-mode "gpgme/ctx-keylist-mode ok" "$core" has gpgme/ctx-pinentry-mode "gpgme/ctx-pinentry-mode ok" "$core" has gpgme/ctx-include-certs "gpgme/ctx-include-certs ok" "$core" has gpgme/ctx-set-sender "gpgme/ctx-set-sender ok" "$core" has gpgme/ctx-get-sender "gpgme/ctx-get-sender ok" "$core" has gpgme/ctx-passphrase-cb "gpgme/ctx-passphrase-cb ok" "$core" has gpgme/ctx-progress-cb "gpgme/ctx-progress-cb ok" "$core" has gpgme/ctx-release "gpgme/ctx-release ok" "$core" # signature notations -- the one attribute with a list behind it has gpgme/notation-empty "gpgme/notation-empty ok" "$core" has gpgme/notation-add "gpgme/notation-add ok" "$core" has gpgme/notation-readback "gpgme/notation-readback ok" "$core" has gpgme/notation-policy-url "gpgme/notation-policy-url ok" "$core" has gpgme/notation-clear "gpgme/notation-clear ok" "$core" # the data layer: memory, growing memory, a descriptor, and the callback vtable. # gpgme_data_t is what every operation reads from and writes to, so a wrong seek # or a short read here would corrupt a signature rather than fail an assertion. has gpgme/data-new-from-mem "gpgme/data-new-from-mem ok" "$core" has gpgme/data-read "gpgme/data-read ok" "$core" has gpgme/data-read-eof "gpgme/data-read-eof ok" "$core" has gpgme/data-seek-set "gpgme/data-seek-set ok" "$core" has gpgme/data-read-partial "gpgme/data-read-partial ok" "$core" has gpgme/data-seek-cur "gpgme/data-seek-cur ok" "$core" has gpgme/data-seek-end "gpgme/data-seek-end ok" "$core" has gpgme/data-encoding-default "gpgme/data-encoding-default ok" "$core" has gpgme/data-set-encoding "gpgme/data-set-encoding ok" "$core" has gpgme/data-get-encoding "gpgme/data-get-encoding ok" "$core" has gpgme/data-set-file-name "gpgme/data-set-file-name ok" "$core" has gpgme/data-get-file-name "gpgme/data-get-file-name ok" "$core" has gpgme/data-new "gpgme/data-new ok" "$core" has gpgme/data-write "gpgme/data-write ok" "$core" has gpgme/data-write-seek "gpgme/data-write-seek ok" "$core" has gpgme/data-write-readback "gpgme/data-write-readback ok" "$core" has gpgme/data-release-and-get-mem "gpgme/data-release-and-get-mem ok" "$core" has gpgme/data-new-from-cbs "gpgme/data-new-from-cbs ok" "$core" has gpgme/data-cbs-read "gpgme/data-cbs-read ok" "$core" has gpgme/data-cbs-seek "gpgme/data-cbs-seek ok" "$core" has gpgme/data-cbs-release "gpgme/data-cbs-release ok" "$core" has gpgme/data-new-from-file "gpgme/data-new-from-file ok" "$core" has gpgme/data-file-read "gpgme/data-file-read ok" "$core" # armor parsing with no engine: a real ed25519 public key must come back as a # PGP KEY and not merely as "some armored block", and plain text as unknown. has gpgme/data-identify-key "gpgme/data-identify-key ok" "$core" has gpgme/data-identify-plain "gpgme/data-identify-plain ok" "$core" # engine discovery, and the refusal path when the engine is not there has gpgme/engine-info "gpgme/engine-info ok" "$core" has gpgme/engine-info-nonempty "gpgme/engine-info-nonempty ok" "$core" has gpgme/ctx-engine-info "gpgme/ctx-engine-info ok" "$core" has gpgme/missing-engine-refused "gpgme/missing-engine-refused ok" "$core" # --- anti-truncation: the probe RAN, rather than the suite reaching its end --- # A probe killed halfway prints no GPGME-END, and every `has` above would then # report the same missing needle -- which reads like 80 broken assertions instead # of one truncated run. These three name what actually happened. endline=$(printf '%s\n' "$core" | grep '^GPGME-END ' | head -n 1) has gpgme/core-end "GPGME-END " "$endline" check_eq gpgme/core-case-count 76 "$(printf '%s\n' "$endline" | sed -n 's/.*cases=\([0-9]*\).*/\1/p')" check_eq gpgme/core-no-fails 0 "$(printf '%s\n' "$endline" | sed -n 's/.*fails=\([0-9]*\).*/\1/p')" # --- the same transcript, byte for byte, as the x86_64 control --------------- # This is the case that catches a target binary whose checks stop running rather # than fail: a per-case assertion can only see the lines that were printed. native=$BST_DATA/gpgme-native.txt if [ ! -s "$native" ]; then bst_notok gpgme/native-shipped "absent or empty: $native" elif [ "$core" = "$(cat "$native")" ]; then bst_ok gpgme/native-identical elif command -v diff >/dev/null 2>&1; then bst_notok gpgme/native-identical \ "$(_bst_1line "$(printf '%s\n' "$core" | diff - "$native" 2>&1 | head -n 6)")" else # No diff applet on this image: say how they differ in size rather than say # nothing, so the case is still actionable. bst_notok gpgme/native-identical \ "target $(printf '%s\n' "$core" | wc -l) lines vs control $(wc -l < "$native")" fi # --- the engine half: the ticket's pass criterion ---------------------------- # Scratch GNUPGHOME under $TMPDIR, never the real one: a suite that writes into a # user's keyring is a suite nobody can run twice. gnupghome=${TMPDIR:-/tmp}/bst-gpgme-home.$$ mkdir -p "$gnupghome" 2>/dev/null && chmod 700 "$gnupghome" 2>/dev/null eng=$(GNUPGHOME=$gnupghome "$probe" --engine 2>&1) # Each case reports ok when an engine is present and skip when it is not; the # suite asserts the LINE, so a genuine failure is neither and lands as not ok. for c in engine-version dirinfo-homedir dirinfo-gpg-name engine-info-openpgp \ ctx-set-engine-info ctx-engine-info-readback engine-home genkey \ keylist-secret keylist-count keylist-uid keylist-fpr keylist-capable \ sign sign-fpr sign-armored \ verify verify-summary verify-fpr verify-plaintext verify-tampered; do line=$(printf '%s\n' "$eng" | grep "^GPGME gpgme/$c " | head -n 1) case "$line" in *" ok") bst_ok "gpgme/$c" ;; *" SKIP "*) bst_skip "gpgme/$c" "${line#*SKIP }" ;; "") bst_notok "gpgme/$c" "the probe printed no line for this case" ;; *) bst_notok "gpgme/$c" "$(_bst_1line "${line#*FAIL }")" ;; esac done endline=$(printf '%s\n' "$eng" | grep '^GPGME-END ' | head -n 1) has gpgme/engine-end "GPGME-END " "$endline" check_eq gpgme/engine-case-count 21 "$(printf '%s\n' "$endline" | sed -n 's/.*cases=\([0-9]*\).*/\1/p')" check_eq gpgme/engine-no-fails 0 "$(printf '%s\n' "$endline" | sed -n 's/.*fails=\([0-9]*\).*/\1/p')" rm -rf "$gnupghome" rm -f "$probe"