. "$BST_LIB" # libusbgx 0.2.0+git (SRCREV ec0b01c0) -- the USB *gadget* (device-side) # configfs library. It composes a gadget under /sys/kernel/config/usb_gadget out # of functions (acm, ecm, ffs, hid, mass storage, midi, uac2, uvc, printer) and # binds it to a UDC. It is NOT libusb1, which is the host side and unrelated # code; the two ports landed a day apart and the names invite the confusion. # # THIS IMAGE HAS NO GADGET SUBSTRATE AT ALL, and that is a kernel-config fact, # not a missing module: arch/wasm32/configs/wasm32_defconfig sets neither # CONFIG_CONFIGFS_FS nor CONFIG_USB_GADGET, so configfs cannot even be MOUNTED # and /sys/class/udc does not exist. CONFIG_USB *is* on -- usb, hub, usb-serial # and ftdi_sio are built for the WebUSB host bridge -- and that is exactly the # fact that makes "USB is enabled" the wrong thing to reason from here. # # So the suite is in three parts. # # 1. THE SHIPPED ARTIFACTS, asserted for real. 13 headers, libusbgx.pc, # LibUsbgxConfig.cmake, libusbgx.a, /etc/default/usbgx, 17 programs -- plus # a completeness check that reads EVERY public entry point the INSTALLED # headers declare (164 of them) and requires each in the archive, so a # partial compile, a visibility mistake or a header/library skew all fail # it. The assertion tracks the headers rather than a list written here. # # 2. THE SIX PROGRAMS THAT RUN TO A DECISION HERE. Three validate their # arguments BEFORE calling usbg_init, so they reach a real verdict on a # machine with no configfs: gadget-export, gadget-import and # gadget-vid-pid-remove. Three refuse at usbg_init with a specific exit # status and a specific pair of stderr lines: show-gadgets, gadget-acm-ecm # and show-udcs. Both are conditioned on the probe below -- on a machine # that GROWS configfs the refusal cases skip instead of reddening, because # such a machine is a healthier one and a suite that reds on it is wrong. # # 3. WHAT CANNOT BE ANSWERED HERE, skipped against the probe rather than # omitted: everything that creates, binds or enumerates a gadget. # # THE TICKET'S PREMISE ABOUT USAGE TEXT DOES NOT HOLD, and the correction is # recorded here rather than quietly worked around. show-gadgets and # gadget-acm-ecm are `int main(void)` -- they take no arguments, print no usage # and validate nothing. The three that do are named above. gadget-start and # gadget-stop are /bin/sh scripts, not C, and take no arguments either. # # HOW THE ARCHIVE IS SEARCHED, and why it is not the way 99-libusb1.test does # it. libusbgx.a is an ar of LLVM IR bitcode. Its ar SYMBOL INDEX NUL-separates # the exported names, while the bitcode STRING TABLE concatenates identifiers # with no separator at all, so a flatten of non-printable runs to newlines gives # one name per line in the first and long glued runs in the second. That makes # both obvious greps wrong, in opposite directions and both silently: # # grep -oFf over-reports -- a needle that is a prefix of another token is # claimed as found. Measured on THIS archive: all four of # usbg_ini, usbg_cleanu, usbg_get_gadget_nam and # usbg_lookup_function_typ are "found", and none exists. # grep -owFf under-reports -- the guard 99-libusb1.test added for exactly the # case above. The index's FIRST name is preceded by the last byte # of the offsets table, which in this archive is a printable 'z' # (0x7A at offset 1039), so -w refuses it and # usbg_lookup_function_type reads as MISSING from an archive whose # index exports it. 163 of 164 -- a false red, one symbol short. # # Anchoring each needle at END OF RUN is sound in both regions: every indexed # name is NUL-terminated and so ends a line, while a mere prefix inside a glued # bitcode run never does. Measured: 164 of 164 found, and none of the four # near-misses above. That is why the lookup below is `grep -oEf` over a pattern # file of NAME$ rather than a plain -F list. # # The files are read ONCE into shell variables and every text assertion is a # `case` glob against them -- no fork per case -- because on the wasm32-hwjs # runtime the forking IS the cost, not the work. inc=/usr/include/usbg pc=/usr/lib/pkgconfig/libusbgx.pc cmk=/usr/lib/cmake/LibUsbgx/LibUsbgxConfig.cmake lib=/usr/lib/libusbgx.a dflt=/etc/default/usbgx tmp=${TMPDIR:-/tmp} nl=' ' # hasfile -- ok iff is a non-empty regular file. # nofile -- ok iff does not exist. # has -- ok iff contains the literal . # hasnt -- ok iff it does not. # hasline -- ok iff carries as a WHOLE line. # noline -- ok iff it does not. 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; } noline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_notok "$1" "present and must not be: $2" ;; *) bst_ok "$1" ;; esac; } # --- the shipped headers ------------------------------------------------------ hasfile libusbgx/hdr-usbg "$inc/usbg.h" hasfile libusbgx/hdr-version "$inc/usbg_version.h" for h in ffs hid loopback midi ms net phonet printer serial uac2 uvc; do hasfile "libusbgx/hdr-fn-$h" "$inc/function/$h.h" done # --- the other shipped files -------------------------------------------------- hasfile libusbgx/pc-present "$pc" hasfile libusbgx/cmake-present "$cmk" hasfile libusbgx/archive-present "$lib" hasfile libusbgx/default-present "$dflt" # --- the shipped programs ----------------------------------------------------- # # update-alternatives owns two of them: the recipe declares ALTERNATIVE for # gadget-acm-ecm and show-gadgets, so the real files are installed with a # .libusbgx suffix and the plain names are links made at rootfs assembly time # (host-side -- VIRTUAL-RUNTIME_update-alternatives is empty on this distro, so # there is no on-device switching). BOTH ends are asserted: a missing link is # the failure mode that would leave the tools unreachable under the names the # documentation, the systemd unit and every example use. for b in gadget-acm-ecm show-gadgets; do hasfile "libusbgx/bin-$b-real" "/usr/bin/$b.libusbgx" if [ -x "/usr/bin/$b" ]; then bst_ok "libusbgx/bin-$b-alt" else bst_notok "libusbgx/bin-$b-alt" "alternatives link /usr/bin/$b missing or not executable"; fi done for b in gadget-export gadget-ffs gadget-hid gadget-import gadget-midi gadget-ms \ gadget-printer gadget-rndis-os-desc gadget-uac2 gadget-uvc \ gadget-vid-pid-remove show-udcs; do if [ -x "/usr/bin/$b" ]; then bst_ok "libusbgx/bin-$b" else bst_notok "libusbgx/bin-$b" "absent or not executable: /usr/bin/$b"; fi done for b in gadget-start gadget-stop; do if [ -x "/usr/bin/$b" ]; then bst_ok "libusbgx/bin-$b" else bst_notok "libusbgx/bin-$b" "absent or not executable: /usr/bin/$b"; fi done # --- NOTHING CAN ENABLE OR START usbgx ---------------------------------------- # # The recipe ships a systemd unit and a sysvinit script in its SRC_URI, and # installs NEITHER here: do_install:append gates the first on DISTRO_FEATURES # 'systemd' and the second on 'sysvinit', and DISTRO_FEATURES carries neither # on this distro. INHIBIT_UPDATERCD_BBCLASS is 1 whenever 'examples' is in # PACKAGECONFIG, so update-rc.d lays down no rc?.d symlink either. These cases # assert that outcome rather than trusting the reasoning: a DISTRO_FEATURES # change elsewhere would otherwise start shipping a unit that wants # sys-kernel-config.mount on a kernel with no configfs. nofile libusbgx/no-systemd-unit /lib/systemd/system/usbgx.service nofile libusbgx/no-systemd-unit-usr /usr/lib/systemd/system/usbgx.service nofile libusbgx/no-initscript /etc/init.d/usbgx rcl=$(ls -d /etc/rc*.d/*usbgx 2>/dev/null) if [ -z "$rcl" ]; then bst_ok libusbgx/no-rc-links else bst_notok libusbgx/no-rc-links "rc?.d links present: $(_bst_1line "$rcl")"; fi # --- static-only guards ------------------------------------------------------- # # The distro appends --disable-shared at class-target scope and there is no # dynamic loader on this arm, so a .so or .la here would mean the recipe reached # configure by some other route. nofile libusbgx/no-shared-usr /usr/lib/libusbgx.so nofile libusbgx/no-shared-base /lib/libusbgx.so nofile libusbgx/no-libtool /usr/lib/libusbgx.la # --- the installed headers ---------------------------------------------------- vt=$(cat "$inc/usbg_version.h" 2>/dev/null) has libusbgx/hdr-version-macro '#define USBG_VERSION 0x00020000' "$vt" has libusbgx/hdr-version-make '#define USBG_MAKE_VERSION(major, minor, micro)' "$vt" hn=$tmp/libusbgx-hneedles.$$ printf '%s\n' \ 'extern int usbg_init(const char *configfs_path, usbg_state **state);' \ 'extern void usbg_cleanup(usbg_state *s);' \ 'extern const char *usbg_error_name(usbg_error e);' \ 'extern const char *usbg_strerror(usbg_error e);' \ 'extern int usbg_lookup_function_type(const char *name);' \ 'extern usbg_gadget *usbg_get_gadget(usbg_state *s, const char *name);' \ 'extern usbg_udc *usbg_get_udc(usbg_state *s, const char *name);' \ 'extern int usbg_enable_gadget(usbg_gadget *g, usbg_udc *udc);' \ 'extern int usbg_disable_gadget(usbg_gadget *g);' \ 'typedef enum' \ 'USBG_SUCCESS = 0,' \ 'USBG_ERROR_NOT_FOUND = -4,' \ 'USBG_ERROR_IO = -5,' \ 'USBG_ERROR_NOT_SUPPORTED = -9,' \ 'USBG_ERROR_OTHER_ERROR = -99' \ 'USBG_F_ACM,' \ 'USBG_F_ECM,' \ 'USBG_F_RNDIS,' \ 'USBG_F_MASS_STORAGE,' \ 'struct usbg_gadget_attrs' \ 'struct usbg_gadget_strs' \ 'struct usbg_config_attrs' \ 'struct usbg_config_strs' \ 'struct usbg_gadget_os_descs' \ > "$hn" hf=$(grep -oFf "$hn" "$inc/usbg.h" 2>/dev/null | sort -u) # The five entry points a consumer cannot do without. usbg_lookup_function_type # is in this list on purpose: it is the one symbol the -w formulation loses (see # the header comment), so its presence is pinned in the header as well as in the # archive, and a red in only one of the two places names which side broke. hasline libusbgx/hdr-decl-init 'extern int usbg_init(const char *configfs_path, usbg_state **state);' "$hf" hasline libusbgx/hdr-decl-cleanup 'extern void usbg_cleanup(usbg_state *s);' "$hf" hasline libusbgx/hdr-decl-errorname 'extern const char *usbg_error_name(usbg_error e);' "$hf" hasline libusbgx/hdr-decl-strerror 'extern const char *usbg_strerror(usbg_error e);' "$hf" hasline libusbgx/hdr-decl-lookupfn 'extern int usbg_lookup_function_type(const char *name);' "$hf" hasline libusbgx/hdr-decl-getgadget 'extern usbg_gadget *usbg_get_gadget(usbg_state *s, const char *name);' "$hf" hasline libusbgx/hdr-decl-getudc 'extern usbg_udc *usbg_get_udc(usbg_state *s, const char *name);' "$hf" hasline libusbgx/hdr-decl-enable 'extern int usbg_enable_gadget(usbg_gadget *g, usbg_udc *udc);' "$hf" hasline libusbgx/hdr-decl-disable 'extern int usbg_disable_gadget(usbg_gadget *g);' "$hf" # Five enumerator VALUES, not just their names. USBG_ERROR_NOT_FOUND = -4 is the # one this image will actually produce (see the runtime section), so it is # pinned with its number; USBG_ERROR_IO = -5 sits next to it and is included # because -5 is the value a reader guesses for NOT_FOUND from its position in # the list, and pinning both is what makes a future renumbering unambiguous. # USBG_ERROR_OTHER_ERROR is the LAST enumerator and carries no trailing comma. hasline libusbgx/hdr-val-success 'USBG_SUCCESS = 0,' "$hf" hasline libusbgx/hdr-val-notfound 'USBG_ERROR_NOT_FOUND = -4,' "$hf" hasline libusbgx/hdr-val-io 'USBG_ERROR_IO = -5,' "$hf" hasline libusbgx/hdr-val-notsup 'USBG_ERROR_NOT_SUPPORTED = -9,' "$hf" hasline libusbgx/hdr-val-other 'USBG_ERROR_OTHER_ERROR = -99' "$hf" # The function-type enumerators and the attribute structs ARE the gadget model # as libusbgx presents it; a header that lost one would still compile a trivial # consumer and fail every real one. hasline libusbgx/hdr-ftype-acm 'USBG_F_ACM,' "$hf" hasline libusbgx/hdr-ftype-ecm 'USBG_F_ECM,' "$hf" hasline libusbgx/hdr-ftype-rndis 'USBG_F_RNDIS,' "$hf" hasline libusbgx/hdr-ftype-ms 'USBG_F_MASS_STORAGE,' "$hf" hasline libusbgx/hdr-struct-gattrs 'struct usbg_gadget_attrs' "$hf" hasline libusbgx/hdr-struct-gstrs 'struct usbg_gadget_strs' "$hf" hasline libusbgx/hdr-struct-cattrs 'struct usbg_config_attrs' "$hf" hasline libusbgx/hdr-struct-cstrs 'struct usbg_config_strs' "$hf" hasline libusbgx/hdr-struct-osdesc 'struct usbg_gadget_os_descs' "$hf" # --- pkg-config --------------------------------------------------------------- pct=$(cat "$pc" 2>/dev/null) has libusbgx/pc-name 'Name: libusbgx' "$pct" has libusbgx/pc-version 'Version: 0.2.0' "$pct" has libusbgx/pc-libs 'Libs: -L${libdir} -lusbgx' "$pct" has libusbgx/pc-cflags 'Cflags: -I${includedir}' "$pct" has libusbgx/pc-libdir 'libdir=/usr/lib' "$pct" # Requires: libconfig is the PACKAGECONFIG decision seen from the consumer's # side, and it is not decoration: the library really does reference libconfig # (asserted in the archive below), so a static consumer that did not pick up # -lconfig from here would fail to link. libconfig-dev + libconfig-staticdev are # on this image, so the requirement is satisfiable. has libusbgx/pc-requires 'Requires: libconfig' "$pct" # --- the cmake package config ------------------------------------------------- # # Upstream's LibUsbgxConfig.cmake.in is a STATIC TEMPLATE, not generated from # the build: it says `SHARED IMPORTED` and IMPORTED_LOCATION .../libusbgx.so # whatever configure decided. On a --disable-shared distro that names a file # that is not on the image, and find_package(LibUsbgx) SUCCEEDS before handing # the consumer a dead location -- so the failure surfaces at link time in the # consumer with no mention of libusbgx. The bbappend rewrites it to the archive # that IS shipped. Both halves are asserted: the repair took, and no trace of # the .so is left. cmt=$(cat "$cmk" 2>/dev/null) has libusbgx/cmake-static 'STATIC IMPORTED' "$cmt" has libusbgx/cmake-location '${_IMPORT_PREFIX}/lib/libusbgx.a' "$cmt" hasnt libusbgx/cmake-no-so 'libusbgx.so' "$cmt" hasnt libusbgx/cmake-no-shared 'SHARED IMPORTED' "$cmt" has libusbgx/cmake-target 'LibUsbgx::LibUsbgx' "$cmt" has libusbgx/cmake-includes 'set(LibUsbgx_INCLUDE_DIRS' "$cmt" # --- /etc/default/usbgx ------------------------------------------------------- # # Both lists are empty, which is what makes gadget-start and gadget-stop safe to # run below: with no schemas named, neither loop iterates and neither script # touches configfs at all. dt=$(cat "$dflt" 2>/dev/null) has libusbgx/default-import 'IMPORT_SCHEMAS=""' "$dt" has libusbgx/default-enabled 'ENABLED_SCHEMAS="$IMPORT_SCHEMAS"' "$dt" # --- the archive -------------------------------------------------------------- check_eq libusbgx/archive-format '!' "$(head -c 7 "$lib" 2>/dev/null)" # Flatten every run of printable bytes onto its own line. \n is deliberately NOT # in the preserved set: the ar symbol index separates names with NUL, so this is # what puts one exported name per line. The octal range rather than [:print:] is # the formulation both greps and both trs agree on (derived by 69-libb64.test, # and the tr that runs here is busybox's applet, not coreutils' -- ash resolves # applets ahead of $PATH whatever /usr/bin/tr's alternatives link points at). flat=$tmp/libusbgx-flat.$$ ( tr -c '\40-\176' '\n' < "$lib" > "$flat" 2>/dev/null ) # WHICH TRANSLATION UNITS ARE IN THE ARCHIVE, by each one's recorded source # path. Sixteen are expected. The pair that matters is the last two against # usbg_schemes_none.c: src/Makefile.am compiles usbg_schemes_libconfig.c + # usbg_common_libconfig.c when gadget-schemes and libconfig are both on, and # usbg_schemes_none.c -- whose import/export entry points all return # USBG_ERROR_NOT_SUPPORTED -- when they are not. Which of the two is in the # archive IS the PACKAGECONFIG decision, visible in the artifact rather than # inferred from the recipe. tuf=$tmp/libusbgx-tu.$$ printf '%s\n' \ 'src/usbg.c' 'src/usbg_error.c' 'src/usbg_common.c' \ 'src/usbg_schemes_libconfig.c' 'src/usbg_common_libconfig.c' \ 'src/usbg_schemes_none.c' \ 'src/function/ether.c' 'src/function/ffs.c' 'src/function/hid.c' \ 'src/function/loopback.c' 'src/function/midi.c' 'src/function/ms.c' \ 'src/function/phonet.c' 'src/function/printer.c' 'src/function/serial.c' \ 'src/function/uac2.c' 'src/function/uvc.c' \ 'libusbgx/0.2.0+git' \ > "$tuf" # -oF without -w or an anchor here: these needles carry '/' and '.', and each # appears as the tail of a longer recorded path ('../../git/src/usbg.c'), so # neither a word boundary nor an end-of-run anchor applies. None of them is a # substring of another except src/usbg.c inside nothing, so plain -F is exact # enough -- and the two that must NOT be found are checked the same way. tufound=$(grep -oFf "$tuf" "$flat" 2>/dev/null | sort -u) for f in usbg usbg_error usbg_common usbg_schemes_libconfig usbg_common_libconfig; do hasline "libusbgx/tu-$f" "src/$f.c" "$tufound" done for f in ether ffs hid loopback midi ms phonet printer serial uac2 uvc; do hasline "libusbgx/tu-fn-$f" "src/function/$f.c" "$tufound" done noline libusbgx/tu-no-schemes-none 'src/usbg_schemes_none.c' "$tufound" # The version, recorded in the archive's own debug paths rather than only in the # .pc, so the compiled code is pinned to the version the header claims. hasline libusbgx/lib-version-path 'libusbgx/0.2.0+git' "$tufound" # libconfig is REFERENCED by the compiled code, not merely named in the .pc. # These are undefined references, so they live in the bitcode string table glued # between other identifiers rather than in the NUL-separated index -- an # end-of-run anchor would find none of them and a word boundary would find none # either. Plain -F is the only formulation that applies, and it is safe for a # hand-picked set of long distinctive names. for s in config_init config_destroy config_setting_length config_setting_get_elem config_setting_get_int; do if grep -qF "$s" "$flat" 2>/dev/null; then bst_ok "libusbgx/ref-$s" else bst_notok "libusbgx/ref-$s" "no reference to $s in the archive"; fi done # --- completeness: every declared entry point is in the archive ---------------- # # TWO declaration styles have to be read, and missing the second is how this # check would silently cover only a third of the API: usbg.h marks its public # functions with a leading `extern`, while every function/*.h declares them bare # at line start (their only `extern` is the C++ `extern "C" {` guard). Both are # taken; `static inline` is excluded, since those are header-only and correctly # absent from the archive. namef=$tmp/libusbgx-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:]]*\(' \ "$inc/usbg.h" "$inc/function/"*.h 2>/dev/null \ | grep -v 'static' \ | sed -E 's/[[:space:]]*\($//' | sed -E 's/^.*[^A-Za-z0-9_]//' \ | grep -E '^usbg_' | sort -u > "$namef" # End-of-run anchored, one batched pass. See the header comment for the two # rival formulations and what each gets wrong on THIS archive. patf=$tmp/libusbgx-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: headers that lost their declarations, or an # extraction that silently matched nothing, would otherwise pass vacuously with # zero names to look up. 0.2.0 declares 164. if [ "$ndecl" -ge 150 ] && [ "$nfound" -eq "$ndecl" ]; then bst_ok libusbgx/sym-complete else bst_notok libusbgx/sym-complete "declared=$ndecl found=$nfound"; fi # THE OVER-REPORT CONTROL, run against the same archive and the same matcher. # Four names that do not exist, each a prefix of one that does. `grep -oFf` # reports all four; the anchored form must report none. Without this case the # completeness count above could be inflated by the matcher itself and still # read as a full green. fakef=$tmp/libusbgx-fake.$$ printf '%s\n' 'usbg_ini$' 'usbg_cleanu$' 'usbg_get_gadget_nam$' 'usbg_lookup_function_typ$' > "$fakef" fake=$(grep -oEf "$fakef" "$flat" 2>/dev/null | sort -u) nfake=$(printf '%s' "$fake" | grep -c .) if [ "$nfake" -eq 0 ]; then bst_ok libusbgx/sym-no-prefix-inflation else bst_notok libusbgx/sym-no-prefix-inflation "matcher claimed $nfake non-existent names: $(_bst_1line "$fake")"; fi # A named sample across the layers as well, so a failure names the layer that # broke rather than only reporting a count. for s in usbg_init usbg_cleanup usbg_get_configfs_path usbg_lookup_function_type \ usbg_create_gadget usbg_create_gadget_vid_pid usbg_rm_gadget \ usbg_create_function usbg_rm_function usbg_create_config usbg_rm_config \ usbg_add_config_function usbg_enable_gadget usbg_disable_gadget \ usbg_get_gadget usbg_get_first_gadget usbg_get_next_gadget \ usbg_get_udc usbg_get_first_udc usbg_get_udc_gadget usbg_get_gadget_udc \ usbg_get_gadget_attrs usbg_set_gadget_attrs usbg_get_gadget_strs \ usbg_import_gadget usbg_export_gadget usbg_import_function usbg_export_config \ usbg_error_name usbg_strerror \ usbg_f_ms_create_lun usbg_f_net_get_attrs usbg_f_hid_set_attrs \ usbg_to_ms_function usbg_from_net_function; do hasline "libusbgx/sym-$s" "$s" "$found" done rm -f "$hn" "$tuf" "$namef" "$patf" "$fakef" # --- the runtime probe --------------------------------------------------------- # # Established from the machine, not from this file's assumptions, and reported # verbatim in every skip reason 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 cfsdir=absent; [ -d /sys/kernel/config ] && cfsdir=present gdir=absent; [ -d /sys/kernel/config/usb_gadget ] && gdir=present udcdir=absent; [ -d /sys/class/udc ] && udcdir=present nudc=0 if [ -d /sys/class/udc ]; then for d in /sys/class/udc/*; do [ -e "$d" ] && nudc=$((nudc + 1)); done fi probe="configfs:/proc/filesystems=$cfsfs mount=$cfsmnt /sys/kernel/config=$cfsdir usb_gadget=$gdir udc=$udcdir udcs=$nudc" # --- argument validation: runs to a verdict with or without configfs ----------- # # These three check their argv BEFORE calling usbg_init, so they are the part of # the shipped code that reaches a real decision on this machine. Each returns # -EINVAL from main, which the shell sees as 234 (-22 truncated to a byte) -- # asserted as a number rather than as "non-zero", because "non-zero" is also # what a wasm module that failed to instantiate would give. uo=$(gadget-export 2>&1); urc=$? has libusbgx/export-usage 'Usage: gadget-export gadget_name file_name' "$uo" check_eq libusbgx/export-usage-rc 234 "$urc" uo=$(gadget-import 2>&1); urc=$? has libusbgx/import-usage 'Usage: gadget-import gadget_name file_name' "$uo" check_eq libusbgx/import-usage-rc 234 "$urc" # gadget-vid-pid-remove takes vid:pid and rejects an argument with no colon. # Note it accepts ZERO arguments (it then falls through to its built-in default # VID/PID and calls usbg_init), so the malformed argument is the only way to # reach its validation branch. uo=$(gadget-vid-pid-remove nocolonhere 2>&1); urc=$? has libusbgx/vidpid-usage 'Usage: gadget-vid-pid-remove vid:pid' "$uo" check_eq libusbgx/vidpid-usage-rc 234 "$urc" # --- the configfs-absent refusal ------------------------------------------------ # # usbg_init() opens /usb_gadget and translates the errno: ENOENT # becomes USBG_ERROR_NOT_FOUND (-4), whose usbg_strerror text is "Not found # (file or directory removed)". Each caller then prints its own first line -- # show-gadgets and gadget-acm-ecm say "gadget init", show-udcs says "state init" # -- and returns -EINVAL, i.e. 234. The library ALSO prints an ERRORNO line from # inside usbg_init itself, naming the function and the raw strerror. # # All of this is conditional on the probe: on a machine that grows configfs and # a gadget tree these commands would succeed with an empty listing instead, and # reddening there would be wrong. if [ "$gdir" = absent ]; then for b in show-gadgets gadget-acm-ecm; do o=$("$b" 2>&1); rc=$? has "libusbgx/$b-refuses" 'Error on USB gadget init' "$o" has "libusbgx/$b-errname" 'Error: USBG_ERROR_NOT_FOUND' "$o" has "libusbgx/$b-errtext" 'Not found (file or directory removed)' "$o" check_eq "libusbgx/$b-rc" 234 "$rc" done o=$(show-udcs 2>&1); rc=$? has libusbgx/show-udcs-refuses 'Error on USB state init' "$o" has libusbgx/show-udcs-errname 'Error: USBG_ERROR_NOT_FOUND' "$o" check_eq libusbgx/show-udcs-rc 234 "$rc" # The library's own diagnostic, from usbg_init's ERRORNO macro. It is what # distinguishes "the directory is missing" from every other init failure, and # it is the line a user would actually be told. o=$(show-gadgets 2>&1) has libusbgx/usbg-init-errno 'usbg_init() No such file or directory' "$o" else for c in show-gadgets-refuses show-gadgets-errname show-gadgets-errtext show-gadgets-rc \ gadget-acm-ecm-refuses gadget-acm-ecm-errname gadget-acm-ecm-errtext gadget-acm-ecm-rc \ show-udcs-refuses show-udcs-errname show-udcs-rc usbg-init-errno; do bst_skip "libusbgx/$c" "a usb_gadget configfs tree exists, so usbg_init would not refuse -- $probe" done fi # --- gadget-start / gadget-stop ------------------------------------------------- # # These are the two /bin/sh scripts the (absent) service would have driven. They # are run here, and it is safe to run them precisely BECAUSE /etc/default/usbgx # names no schemas: with IMPORT_SCHEMAS and ENABLED_SCHEMAS both empty neither # for-loop iterates, /etc/usbgx.d does not exist so the find contributes nothing, # and neither script writes anywhere. That is asserted rather than assumed -- # the cases below require a clean exit, no output, and no gadget tree afterwards. # # This is NOT "enabling or starting usbgx": there is no unit and no init script # to enable (asserted above), and with an empty schema list these two are # no-ops. Running them is what proves that. so=$(gadget-start 2>&1); src=$? check_eq libusbgx/gadget-start-rc 0 "$src" if [ -z "$so" ]; then bst_ok libusbgx/gadget-start-silent else bst_notok libusbgx/gadget-start-silent "unexpected output: $(_bst_1line "$so")"; fi so=$(gadget-stop 2>&1); src=$? check_eq libusbgx/gadget-stop-rc 0 "$src" if [ -z "$so" ]; then bst_ok libusbgx/gadget-stop-silent else bst_notok libusbgx/gadget-stop-silent "unexpected output: $(_bst_1line "$so")"; fi nofile libusbgx/gadget-start-created-nothing /sys/kernel/config/usb_gadget/g1 # The recipe seds ${bindir} and ${sysconfdir} into both scripts; on this image # those are the same /usr/bin and /etc the upstream files already carried, so # the assertion is that the paths are the ones that exist here -- a prefix # change elsewhere would otherwise leave two scripts calling into nowhere. gs=$(cat /usr/bin/gadget-start 2>&1) has libusbgx/gadget-start-default '/etc/default/usbgx' "$gs" has libusbgx/gadget-start-import '/usr/bin/gadget-import' "$gs" gt=$(cat /usr/bin/gadget-stop 2>&1) has libusbgx/gadget-stop-default '/etc/default/usbgx' "$gt" has libusbgx/gadget-stop-remove '/usr/bin/gadget-vid-pid-remove' "$gt" # --- what cannot be answered here ----------------------------------------------- # # Everything that creates, binds or enumerates a gadget needs a configfs # usb_gadget tree, and this kernel is built with neither CONFIG_CONFIGFS_FS nor # CONFIG_USB_GADGET -- so the filesystem cannot even be mounted, which is a # stronger absence than "nothing is mounted there". Each of these is skipped # against the probe rather than omitted, so the gap is in the report. if [ "$gdir" = absent ]; then bst_skip libusbgx/create-gadget "no configfs usb_gadget tree to create a gadget in -- $probe" bst_skip libusbgx/enumerate-gadgets "no configfs usb_gadget tree to enumerate -- $probe" bst_skip libusbgx/bind-udc "no UDC to bind a gadget to -- $probe" bst_skip libusbgx/import-scheme "gadget-import needs a usb_gadget tree to import into -- $probe" bst_skip libusbgx/export-scheme "gadget-export needs an existing gadget to export -- $probe" else bst_skip libusbgx/create-gadget "a usb_gadget tree exists, but this suite does not mutate a live gadget tree -- $probe" bst_skip libusbgx/enumerate-gadgets "covered by show-gadgets above on this machine -- $probe" bst_skip libusbgx/bind-udc "binding a UDC would take the port live -- $probe" bst_skip libusbgx/import-scheme "importing would mutate a live gadget tree -- $probe" bst_skip libusbgx/export-scheme "no gadget known to be present to export -- $probe" fi rm -f "$flat"