. "$BST_LIB" # libusbg 0.1.0 (meta-oe recipes-support, SRCREV a826d136) -- the ORIGINAL USB # gadget configfs library by Matt Porter, one 2146-line src/usbg.c and one # header, plus the two examples the recipe installs as programs: show-gadgets # and gadget-acm-ecm. # # IT IS NOT libusbgx, which this image already carries (BRINTOS-1577). The two # are separate packages with different APIs, and meta-oe ships them side by # side. That coexistence is the whole substance of this port, and it shows up in # three places, all asserted below rather than reasoned about: # # 1. UPDATE-ALTERNATIVES. Both recipes claim the same two link names, # /usr/bin/show-gadgets and /usr/bin/gadget-acm-ecm. libusbgx-examples sets # ALTERNATIVE_PRIORITY = "90"; libusbg sets none and takes the class # default of 10. So the plain names resolve to LIBUSBGX's programs and # libusbg's are reachable only as *.libusbg. That is asserted by reading # the link, exactly as the ticket asks, and it is deliberately NOT changed: # raising libusbg's priority would silently redirect 99-libusbgx.test's own # show-gadgets cases at a different program. # # 2. THE HEADER. Upstream both packages install , so # libusbg-dev and libusbgx-dev cannot be co-installed -- do_rootfs refuses # two packages owning one path. Our .bbappend moves libusbg's header to # /usr/include/libusbg/usbg/usbg.h and repoints libusbg.pc's Cflags at it, # so `#include ` still resolves for a pkg-config consumer and # libusbgx's copy is untouched. Both halves are asserted, including that # /usr/include/usbg/usbg.h is still LIBUSBGX's -- that case is the # regression guard on the older port. # # 3. THE ARCHIVE NAME. /usr/lib/libusbg.a beside /usr/lib/libusbgx.a. No # conflict, but every glob below is written to tell them apart. # # THERE IS NO .so. The distro appends --disable-shared at class-target scope # (conf/distro/brintos.conf), so the deliverable is the static archive. The # ticket asks for the installed libusbg.so; there is none, and its absence is # pinned rather than left an unexplained gap -- the shape libusbgx, aml and # libdivecomputer already take here. # # THIS IMAGE HAS NO GADGET SUBSTRATE. arch/wasm32/configs/wasm32_defconfig sets # neither CONFIG_CONFIGFS_FS nor CONFIG_USB_GADGET, so /sys/kernel/config cannot # even be mounted. Both shipped programs are `int main(void)` and hardcode # usbg_init("/sys/kernel/config", ...) -- they take no arguments and cannot be # pointed anywhere. So the ticket's "show-gadgets against a fake configfs tree" # is not reachable through show-gadgets, and the parse is driven instead through # the C API, which DOES take the path as an argument. The fake tree is built # under $TMPDIR and handed to libusbg-probe; see the design doc for why the # instrument was narrowed rather than the requirement dropped. lib=/usr/lib/libusbg.a hdr=/usr/include/libusbg/usbg/usbg.h xhdr=/usr/include/usbg/usbg.h pc=/usr/lib/pkgconfig/libusbg.pc tmp=${TMPDIR:-/tmp} nl=' ' hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } nofile() { if [ ! -e "$2" ]; then bst_ok "$1"; else bst_notok "$1" "present and must not be: $2"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } hasnt() { case "$3" in *"$2"*) bst_notok "$1" "present and must not be: $2" ;; *) bst_ok "$1" ;; esac; } hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not present: $2" ;; esac; } # --- the shipped files -------------------------------------------------------- hasfile libusbg/archive-present "$lib" hasfile libusbg/hdr-present "$hdr" hasfile libusbg/pc-present "$pc" nofile libusbg/no-shared-usr /usr/lib/libusbg.so nofile libusbg/no-shared-base /lib/libusbg.so nofile libusbg/no-libtool /usr/lib/libusbg.la # The relocated header is libusbg's, not a copy of libusbgx's: usbg_init takes # the configfs path as its FIRST argument here, which is the API difference # between the two libraries and so the cheapest discriminator. ht=$(cat "$hdr" 2>/dev/null) has libusbg/hdr-is-libusbg 'usbg_init(char *configfs_path, usbg_state **state)' "$ht" has libusbg/hdr-max-str '#define USBG_MAX_STR_LENGTH 256' "$ht" has libusbg/hdr-lang-us-eng 'define LANG_US_ENG' "$ht" hasnt libusbg/hdr-no-usbgx-api 'usbg_lookup_function_type' "$ht" # THE CO-INSTALLATION GUARD, and the reason the .bbappend exists. The # unqualified must still be LIBUSBGX's -- if this ever reads as # libusbg's, the relocation has stopped working and libusbgx-dev has been # silently displaced. xt=$(cat "$xhdr" 2>/dev/null) hasfile libusbg/libusbgx-hdr-still-there "$xhdr" has libusbg/libusbgx-hdr-untouched 'usbg_lookup_function_type' "$xt" hasnt libusbg/libusbgx-hdr-not-ours 'usbg_init(char *configfs_path, usbg_state **state)' "$xt" # --- the pkg-config file ------------------------------------------------------ pt=$(cat "$pc" 2>/dev/null) has libusbg/pc-name 'Name: libusbg' "$pt" has libusbg/pc-version 'Version: 0.1.0' "$pt" has libusbg/pc-libs '-lusbg' "$pt" has libusbg/pc-cflags-moved '-I/usr/include/libusbg' "$pt" # --- the two programs --------------------------------------------------------- # # update-alternatives renames the real files to *.libusbg (ALTERNATIVE_TARGET # defaults to .${BPN}) and owns the plain names. Both ends are read. for b in show-gadgets gadget-acm-ecm; do if [ -x "/usr/bin/$b.libusbg" ]; then bst_ok "libusbg/bin-$b-real" else bst_notok "libusbg/bin-$b-real" "absent or not executable: /usr/bin/$b.libusbg"; fi if [ -x "/usr/bin/$b" ]; then bst_ok "libusbg/alt-$b-present" else bst_notok "libusbg/alt-$b-present" "alternatives link /usr/bin/$b missing or not executable"; fi # WHICH package won the link, read rather than assumed. libusbgx-examples is # at priority 90 and libusbg at the default 10, so the answer is libusbgx -- # and `.libusbg` is a prefix of `.libusbgx`, so this compares the WHOLE # basename and never a substring. tgt=$(readlink "/usr/bin/$b" 2>/dev/null) tgt=${tgt##*/} check_eq "libusbg/alt-$b-target" "$b.libusbgx" "$tgt" done # --- the archive -------------------------------------------------------------- check_eq libusbg/archive-format '!' "$(head -c 7 "$lib" 2>/dev/null)" # Flattened first, then two facts about the compiled code rather than about the # recipe. The needles here carry '/' and '.' and appear as the tail of a longer # recorded path ('../../git/src/usbg.c'), so neither a word boundary nor an # end-of-run anchor applies -- plain -F is the exact formulation for these two, # and the anchored form below is the right one for the exported names. # One exported name per line: the ar symbol index NUL-separates them, so # flattening every run of printable bytes onto its own line splits the index # while gluing the bitcode string table. Needles are anchored at end-of-run for # the reason 99-libusbgx.test records at length: an unanchored -F over-reports # every name that is a prefix of another, and -w under-reports the first name in # the index. flat=$tmp/libusbg-flat.$$ ( tr -c '\40-\176' '\n' < "$lib" > "$flat" 2>/dev/null ) # libusbg is ONE translation unit; a build that dropped it would still produce a # well-formed archive. tufound=$(grep -oF 'src/usbg.c' "$flat" 2>/dev/null | sort -u) hasline libusbg/tu-usbg 'src/usbg.c' "$tufound" # The version, read out of the archive's own recorded paths rather than only out # of the .pc, so the compiled code is pinned to the version the header claims. if grep -qF 'libusbg/0.1.0' "$flat" 2>/dev/null; then bst_ok libusbg/lib-version-path else bst_notok libusbg/lib-version-path 'no libusbg/0.1.0 path recorded in the archive'; fi namef=$tmp/libusbg-names.$$ grep -hoE '^(extern[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*([[:space:]]+[A-Za-z_][A-Za-z0-9_]*)*[[:space:]]*\**[[:space:]]*usbg_[A-Za-z0-9_]*[[:space:]]*\(' \ "$hdr" 2>/dev/null \ | grep -v 'static' \ | sed -E 's/[[:space:]]*\($//' | sed -E 's/^.*[^A-Za-z0-9_]//' \ | grep -E '^usbg_' | sort -u > "$namef" patf=$tmp/libusbg-pats.$$ sed 's/$/$/' "$namef" > "$patf" found=$(grep -oEf "$patf" "$flat" 2>/dev/null | sort -u) ndecl=$(grep -c . "$namef"); nfound=$(printf '%s' "$found" | grep -c .) # The floor guards the check itself: a header that lost its declarations, or an # extraction that matched nothing, would otherwise pass vacuously. 0.1.0 # declares 66. if [ "$ndecl" -ge 60 ] && [ "$nfound" -eq "$ndecl" ]; then bst_ok libusbg/sym-complete else bst_notok libusbg/sym-complete "declared=$ndecl found=$nfound"; fi # The over-report control, same archive and same matcher: four names that do not # exist, each a prefix of one that does. An unanchored -Ff claims all four. fakef=$tmp/libusbg-fake.$$ printf '%s\n' 'usbg_ini$' 'usbg_cleanu$' 'usbg_get_gadget_nam$' 'usbg_get_config_i$' > "$fakef" nfake=$(grep -oEf "$fakef" "$flat" 2>/dev/null | sort -u | grep -c .) if [ "$nfake" -eq 0 ]; then bst_ok libusbg/sym-no-prefix-inflation else bst_notok libusbg/sym-no-prefix-inflation "matcher claimed $nfake non-existent names"; fi for s in usbg_init usbg_cleanup usbg_get_configfs_path usbg_get_gadget \ usbg_get_gadget_attrs usbg_get_gadget_strs usbg_get_gadget_udc \ usbg_get_function usbg_get_function_attrs usbg_get_function_type_str \ usbg_get_config usbg_get_config_attrs usbg_get_config_strs \ usbg_get_binding_name usbg_get_binding_target \ usbg_create_gadget usbg_create_function usbg_create_config \ usbg_add_config_function usbg_enable_gadget usbg_disable_gadget \ usbg_set_gadget_attrs usbg_set_config_max_power usbg_set_net_qmult \ usbg_get_udcs usbg_error_name usbg_strerror; do hasline "libusbg/sym-$s" "$s" "$found" done # libusbgx's own entry points must NOT be in this archive -- the two libraries # are separate code, and a build that somehow linked one into the other would # otherwise pass every case above. for s in usbg_lookup_function_type usbg_f_ms_create_lun usbg_import_gadget; do if grep -qE "^$s\$" "$flat" 2>/dev/null; then bst_notok "libusbg/no-usbgx-sym-$s" "libusbgx entry point present in libusbg.a: $s" else bst_ok "libusbg/no-usbgx-sym-$s"; fi done rm -f "$namef" "$patf" "$fakef" # --- the runtime probe -------------------------------------------------------- # # Established from the machine and quoted verbatim in every skip below. cfsfs=absent; grep -q '[[:space:]]configfs$' /proc/filesystems 2>/dev/null && cfsfs=registered cfsmnt=absent; grep -q ' /sys/kernel/config configfs ' /proc/mounts 2>/dev/null && cfsmnt=mounted gdir=absent; [ -d /sys/kernel/config/usb_gadget ] && gdir=present udcdir=absent; [ -d /sys/class/udc ] && udcdir=present probe="configfs:/proc/filesystems=$cfsfs mount=$cfsmnt usb_gadget=$gdir udc=$udcdir" # --- no configfs: the documented refusal -------------------------------------- # # usbg_init() opendir()s /usb_gadget; ENOENT is translated to # USBG_ERROR_NOT_FOUND (-4). Each program prints its own two lines and returns # -EINVAL, which the shell sees as 234 -- asserted as that number rather than as # "non-zero", because a wasm module that failed to instantiate is also non-zero. # The library itself prints an ERRORNO line naming the function and the raw # strerror; that line is what distinguishes a missing directory from any other # init failure, and it is what a user would actually be shown. if [ "$gdir" = absent ]; then for b in show-gadgets gadget-acm-ecm; do o=$("/usr/bin/$b.libusbg" 2>&1); rc=$? has "libusbg/$b-refuses" 'Error on USB gadget init' "$o" has "libusbg/$b-errname" 'Error: USBG_ERROR_NOT_FOUND' "$o" has "libusbg/$b-errtext" 'Not found (file or directory removed)' "$o" has "libusbg/$b-errno" 'usbg_init() No such file or directory' "$o" check_eq "libusbg/$b-rc" 234 "$rc" done else for c in show-gadgets-refuses show-gadgets-errname show-gadgets-errtext show-gadgets-errno show-gadgets-rc \ gadget-acm-ecm-refuses gadget-acm-ecm-errname gadget-acm-ecm-errtext gadget-acm-ecm-errno gadget-acm-ecm-rc; do bst_skip "libusbg/$c" "a usb_gadget configfs tree exists, so usbg_init would not refuse -- $probe" done fi # --- the fake configfs tree, parsed through the C API ------------------------- # # libusbg reads a gadget tree out of ordinary files and directories, so the # parse is fully exercisable without configfs: the layout below is what # usbg_parse_gadgets/-functions/-configs walk, written with values chosen to be # distinguishable from any default (0x1d6b:0x0104 is gadget-acm-ecm's own pair, # 120 mA and 0x80 are not). G=$tmp/libusbg-cfs.$$ rm -rf "$G" mkdir -p "$G/usb_gadget/g1/strings/0x409" \ "$G/usb_gadget/g1/functions/acm.usb0" \ "$G/usb_gadget/g1/functions/ecm.usb0" \ "$G/usb_gadget/g1/configs/c.1/strings/0x409" # UDC carries a bare newline, which is what an unbound gadget reads as: usbg # fgets() it, so an EMPTY file would be an I/O error rather than "not bound". printf '\n' > "$G/usb_gadget/g1/UDC" printf '0x0200\n' > "$G/usb_gadget/g1/bcdUSB" printf '0x0100\n' > "$G/usb_gadget/g1/bcdDevice" printf '0x02\n' > "$G/usb_gadget/g1/bDeviceClass" printf '0x06\n' > "$G/usb_gadget/g1/bDeviceSubClass" printf '0x01\n' > "$G/usb_gadget/g1/bDeviceProtocol" printf '0x40\n' > "$G/usb_gadget/g1/bMaxPacketSize0" printf '0x1d6b\n' > "$G/usb_gadget/g1/idVendor" printf '0x0104\n' > "$G/usb_gadget/g1/idProduct" printf '0123456789\n' > "$G/usb_gadget/g1/strings/0x409/serialnumber" printf 'Emcraft Systems\n' > "$G/usb_gadget/g1/strings/0x409/manufacturer" printf 'brintOS gadget\n' > "$G/usb_gadget/g1/strings/0x409/product" printf '3\n' > "$G/usb_gadget/g1/functions/acm.usb0/port_num" printf '02:11:22:33:44:55\n' > "$G/usb_gadget/g1/functions/ecm.usb0/dev_addr" printf '02:aa:bb:cc:dd:ee\n' > "$G/usb_gadget/g1/functions/ecm.usb0/host_addr" printf 'usb0\n' > "$G/usb_gadget/g1/functions/ecm.usb0/ifname" printf '5\n' > "$G/usb_gadget/g1/functions/ecm.usb0/qmult" printf '120\n' > "$G/usb_gadget/g1/configs/c.1/MaxPower" printf '0x80\n' > "$G/usb_gadget/g1/configs/c.1/bmAttributes" printf 'CDC ACM+ECM\n' > "$G/usb_gadget/g1/configs/c.1/strings/0x409/configuration" ln -s ../../functions/acm.usb0 "$G/usb_gadget/g1/configs/c.1/acm.usb0" ln -s ../../functions/ecm.usb0 "$G/usb_gadget/g1/configs/c.1/ecm.usb0" probebin=$tmp/bst-libusbg-probe.$$ if [ ! -f "$BST_DATA/libusbg-probe" ]; then bst_notok libusbg/probe-shipped "payload not installed: $BST_DATA/libusbg-probe" elif cp "$BST_DATA/libusbg-probe" "$probebin" 2>/dev/null && chmod +x "$probebin" 2>/dev/null; then bst_ok libusbg/probe-shipped po=$("$probebin" "$G" 2>&1); prc=$? check_eq libusbg/probe-rc 0 "$prc" has libusbg/probe-ran 'probe-end' "$po" # The documented refusal, through the API rather than through a program. hasline libusbg/probe-noent-rc 'noent-rc=-4' "$po" hasline libusbg/probe-noent-name 'noent-name=USBG_ERROR_NOT_FOUND' "$po" hasline libusbg/probe-noent-text 'noent-text=Not found (file or directory removed)' "$po" # The parse. hasline libusbg/probe-init-rc 'init-rc=0' "$po" hasline libusbg/probe-init-name 'init-name=USBG_SUCCESS' "$po" hasline libusbg/probe-configfs-path "configfs-path=$G/usb_gadget" "$po" hasline libusbg/probe-gadget-count 'gadget-count=1' "$po" hasline libusbg/probe-gadget-name 'gadget-name=g1' "$po" hasline libusbg/probe-gadget-udc 'gadget-udc=[]' "$po" hasline libusbg/probe-attrs-rc 'attrs-rc=0' "$po" hasline libusbg/probe-idvendor 'attrs-idVendor=0x1d6b' "$po" hasline libusbg/probe-idproduct 'attrs-idProduct=0x0104' "$po" hasline libusbg/probe-bcdusb 'attrs-bcdUSB=0x0200' "$po" hasline libusbg/probe-bcddevice 'attrs-bcdDevice=0x0100' "$po" hasline libusbg/probe-bdeviceclass 'attrs-bDeviceClass=0x02' "$po" hasline libusbg/probe-bdevicesubclass 'attrs-bDeviceSubClass=0x06' "$po" hasline libusbg/probe-bdeviceprotocol 'attrs-bDeviceProtocol=0x01' "$po" hasline libusbg/probe-bmaxpacket 'attrs-bMaxPacketSize0=0x40' "$po" hasline libusbg/probe-strs-rc 'strs-rc=0' "$po" hasline libusbg/probe-serial 'strs-serial=0123456789' "$po" hasline libusbg/probe-manufacturer 'strs-manufacturer=Emcraft Systems' "$po" hasline libusbg/probe-product 'strs-product=brintOS gadget' "$po" hasline libusbg/probe-function-count 'function-count=2' "$po" hasline libusbg/probe-function-acm 'function=acm.usb0' "$po" hasline libusbg/probe-function-ecm 'function=ecm.usb0' "$po" hasline libusbg/probe-port-num 'function-port_num=3' "$po" hasline libusbg/probe-dev-addr 'function-dev_addr=2:11:22:33:44:55' "$po" hasline libusbg/probe-host-addr 'function-host_addr=2:aa:bb:cc:dd:ee' "$po" hasline libusbg/probe-ifname 'function-ifname=usb0' "$po" hasline libusbg/probe-qmult 'function-qmult=5' "$po" hasline libusbg/probe-config-count 'config-count=1' "$po" hasline libusbg/probe-config-label 'config-label=c' "$po" hasline libusbg/probe-config-id 'config-id=1' "$po" hasline libusbg/probe-config-maxpower 'config-maxpower=120' "$po" hasline libusbg/probe-config-bmattrs 'config-bmattributes=0x80' "$po" hasline libusbg/probe-config-string 'config-configuration=CDC ACM+ECM' "$po" hasline libusbg/probe-lookup-gadget 'lookup-gadget=found' "$po" hasline libusbg/probe-lookup-fn-acm 'lookup-function-acm=found' "$po" hasline libusbg/probe-lookup-fn-ecm 'lookup-function-ecm=found' "$po" hasline libusbg/probe-lookup-fn-absent 'lookup-function-absent=NULL' "$po" hasline libusbg/probe-lookup-config 'lookup-config=found' "$po" hasline libusbg/probe-lookup-config-label 'lookup-config-wrong-label=NULL' "$po" hasline libusbg/probe-lookup-config-id 'lookup-config-absent-id=NULL' "$po" hasline libusbg/probe-lookup-gadget-absent 'lookup-gadget-absent=NULL' "$po" hasline libusbg/probe-cleanup 'cleanup-ok' "$po" # THE BINDINGS ARM IS CONDITIONED ON WHAT readdir() REPORTS, not assumed. # usbg's bindings_select() keeps an entry only when d_type == DT_LNK, so a # filesystem answering DT_UNKNOWN gives a clean parse with zero bindings -- # a true result about the filesystem, not a decode failure. The probe reports # what it saw so the two are never confused. dt=$(printf '%s\n' "$po" | sed -n 's/^dtype-lnk=\([0-9]*\) .*/\1/p') if [ "${dt:-0}" -ge 2 ]; then hasline libusbg/probe-binding-count 'binding-count=2' "$po" hasline libusbg/probe-binding-acm 'binding-name=acm.usb0' "$po" hasline libusbg/probe-binding-ecm 'binding-name=ecm.usb0' "$po" hasline libusbg/probe-binding-tgt-acm 'binding-target=acm.usb0' "$po" hasline libusbg/probe-binding-tgt-ecm 'binding-target=ecm.usb0' "$po" else for c in binding-count binding-acm binding-ecm binding-tgt-acm binding-tgt-ecm; do bst_skip "libusbg/probe-$c" "readdir on $tmp reported $(printf '%s\n' "$po" | grep '^dtype-') -- usbg's bindings_select needs DT_LNK" done fi else bst_notok libusbg/probe-shipped "cannot stage $BST_DATA/libusbg-probe" for c in probe-rc probe-ran probe-init-rc probe-gadget-count; do bst_notok "libusbg/$c" "probe could not be staged" done fi rm -f "$probebin" "$flat" rm -rf "$G" # --- what cannot be answered here --------------------------------------------- # # Everything that CREATES or BINDS a gadget writes to configfs, and a configfs # write is not a file write: the kernel's usb_gadget subsystem interprets it. # Writing the same bytes into the fake tree above would assert nothing about # libusbg and would pin our own fixture as the contract. Skipped against the # probe rather than omitted, so the gap is in the report. bst_skip libusbg/create-gadget "usbg_create_gadget needs a real configfs usb_gadget tree -- $probe" bst_skip libusbg/enable-gadget "usbg_enable_gadget needs a UDC to bind to -- $probe" bst_skip libusbg/set-attrs "the setters write into configfs, which interprets the write -- $probe" bst_skip libusbg/get-udcs "usbg_get_udcs reads /sys/class/udc, absent on this kernel -- $probe"