. "$BST_LIB" # libusb 1.0.27 -- the cross-platform C API for talking to USB devices from # userspace. On Linux it is a thin layer over usbfs: enumeration walks # /sys/bus/usb/devices (or /dev/bus/usb when sysfs is unavailable), a device is # opened as /dev/bus/usb/BBB/DDD, and every transfer is an ioctl on that fd. # # THIS IMAGE HAS NO CONSUMER AND NO BUS, AND BOTH FACTS SHAPE THE SUITE. # # - No consumer: the distro is static-only and has no on-target C compiler, so # nothing on the image links libusb and nothing can be compiled here to. # The whole shipped payload is ONE header, ONE pkg-config file and ONE # archive -- libusb1-dev + libusb1-staticdev; no main package is produced at # all, because with --disable-shared there is no .so for it to carry. # - No bus: the kernel HAS a USB core (usb, hub, usbfs and ftdi_sio are # registered) but no host controller, so /sys/bus/usb/devices is empty and # /dev/bus does not exist. See the probe at the end of this file. # # So every case here asserts the SHIPPED ARTIFACT, in the shape 81-json-c.test # established for a header-and-archive library. What keeps that from being a # file listing is the completeness check: it reads EVERY function the INSTALLED # header declares through LIBUSB_CALL/LIBUSB_CALLV and requires each one in the # archive, so a partial compile, a visibility mistake or a header/library skew # all fail it. The assertion tracks the header rather than a list written here, # which is what makes it survive a version bump. # # --------------------------------------------------------------------------- # THE PORT DECISION IS ASSERTED, NOT ASSUMED # # poky's recipe defaults PACKAGECONFIG to "udev", whose DEPENDS is eudev -- and # eudev's do_compile is refused outright by this toolchain ("[hwjs-coro] REFUSE # fn=mkdir_parents_internal reason=setjmp-2b-unwind-through-non-coroutinizable- # frame"). The bbappend therefore builds with --disable-udev. That is not a # cosmetic switch: it selects which backend translation unit is compiled, and it # changes what libusb_init DOES on a machine like this one. # # find_usbfs_path() (os/linux_usbfs.c) looks for /dev/bus/usb, then /dev for # usbdev*.* nodes; if neither answers it ends in # # #if defined(HAVE_LIBUDEV) || defined(__ANDROID__) # return USB_DEVTMPFS_PATH; /* assume /dev/bus/usb */ # #else # return NULL; # #endif # # so WITH udev, op_init() takes the assumed path and libusb_init reports # SUCCESS on a machine with no USB at all; WITHOUT it, op_init() logs "could # not find usbfs" and libusb_init returns LIBUSB_ERROR_OTHER (-99). # # Both arms were built natively from the identical 1.0.27 source and run with # /dev/bus and /sys/bus/usb bind-mounted empty -- exactly this guest's state: # --enable-udev gave rc=0 with an empty device list, --disable-udev gave -99. # The port thus makes libusb_init HONEST here rather than merely quieter, and # the cases below pin the choice in three independent places: the archive's # translation units, the .pc's Libs.private, and the absence of any libudev # symbol. # # --------------------------------------------------------------------------- # WHY THE ARCHIVE IS GREPPED THE WAY IT IS # # libusb-1.0.a is an ar archive of LLVM IR BITCODE, not of native objects. Its # string table therefore carries identifiers -- symbol names, type and enum # member names, and each translation unit's source path -- but NOT the C string # literals, which bitcode stores as encoded constant arrays. So "could not find # usbfs" is not greppable here and is deliberately not asserted; the source # PATHS are, and they are a stronger statement about which backend was compiled # than any token match could be. # # Symbol lookups use `grep -owFf`: -F because a name is a literal, -f for ONE # batched pass instead of a fork per symbol, and -w because without it a name # that is a PREFIX of another is reported as found. Measured on this archive: # `grep -oFf` claims a hit for the non-existent "libusb_exi" (it sits inside # libusb_exit) while `-owFf` correctly reports none. Getting that wrong inflates # the completeness count in the direction that reads as success. # # 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/libusb-1.0/libusb.h pc=/usr/lib/pkgconfig/libusb-1.0.pc lib=/lib/libusb-1.0.a tmp=${TMPDIR:-/tmp} nl=' ' # hasfile -- ok iff is a non-empty regular file. # nofile ... -- ok iff none of the (already globbed) paths exist. # has -- ok iff contains the literal . # 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; } 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 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; } nofile() { _id=$1; shift; if [ ! -e "$1" ]; then bst_ok "$_id"; else bst_notok "$_id" "present: $1"; fi; } # --- the three shipped files -------------------------------------------------- hasfile libusb1/header-present "$inc" hasfile libusb1/pc-present "$pc" hasfile libusb1/archive-present "$lib" # The archive lands in ${base_libdir}, not ${libdir}: the recipe configures with # --libdir=${base_libdir} and then moves only pkgconfig/ back to /usr/lib. There # is no usrmerge on this distro (DISTRO_FEATURES is empty), so /lib and /usr/lib # are different directories and a consumer that hard-codes -L/usr/lib will not # find the library. That is what the .pc's libdir= line is for, asserted below. nofile libusb1/archive-not-in-usr-lib /usr/lib/libusb-1.0.a # --- the installed header ------------------------------------------------------ # # One batched grep for every needle, then fork-free matching against its result. hn=$tmp/libusb1-hneedles.$$ printf '%s\n' \ '#define LIBUSB_API_VERSION 0x0100010A' \ 'int LIBUSB_CALL libusb_init(libusb_context **ctx);' \ 'void LIBUSB_CALL libusb_exit(libusb_context *ctx);' \ 'const char * LIBUSB_CALL libusb_error_name(int errcode);' \ 'ssize_t LIBUSB_CALL libusb_get_device_list(libusb_context *ctx,' \ 'int LIBUSB_CALLV libusb_set_option(libusb_context *ctx, enum libusb_option option, ...);' \ 'enum libusb_class_code {' \ 'enum libusb_error {' \ 'enum libusb_speed {' \ 'enum libusb_transfer_type {' \ 'enum libusb_descriptor_type {' \ 'struct libusb_device_descriptor {' \ 'struct libusb_config_descriptor {' \ 'struct libusb_interface_descriptor {' \ 'struct libusb_endpoint_descriptor {' \ 'struct libusb_control_setup {' \ 'struct libusb_transfer {' \ 'struct libusb_version {' \ 'LIBUSB_SUCCESS = 0,' \ 'LIBUSB_ERROR_NO_DEVICE = -4,' \ 'LIBUSB_ERROR_NOT_SUPPORTED = -12,' \ 'LIBUSB_ERROR_OTHER = -99' \ 'LIBUSB_CLASS_PER_INTERFACE = 0x00,' \ 'LIBUSB_SPEED_UNKNOWN = 0,' \ 'LIBUSB_TRANSFER_TYPE_CONTROL = 0U,' \ 'LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED = (1 << 0),' \ '#define LIBUSB_HOTPLUG_MATCH_ANY -1' \ > "$hn" hf=$(grep -oFf "$hn" "$inc" 2>/dev/null | sort -u) # The API version macro is the header's own statement of which interface it # publishes; 0x0100010A is 1.0.27's. It is checked as a whole line so a bump # reds here rather than being absorbed by a substring match. hasline libusb1/hdr-api-version '#define LIBUSB_API_VERSION 0x0100010A' "$hf" # The four prototypes a consumer cannot do without, plus the one varargs entry # point (LIBUSB_CALLV, not LIBUSB_CALL -- the distinction the extraction below # has to know about). hasline libusb1/hdr-decl-init 'int LIBUSB_CALL libusb_init(libusb_context **ctx);' "$hf" hasline libusb1/hdr-decl-exit 'void LIBUSB_CALL libusb_exit(libusb_context *ctx);' "$hf" hasline libusb1/hdr-decl-errorname 'const char * LIBUSB_CALL libusb_error_name(int errcode);' "$hf" hasline libusb1/hdr-decl-devlist 'ssize_t LIBUSB_CALL libusb_get_device_list(libusb_context *ctx,' "$hf" hasline libusb1/hdr-decl-setoption 'int LIBUSB_CALLV libusb_set_option(libusb_context *ctx, enum libusb_option option, ...);' "$hf" # The enums and structs that ARE the USB wire format as libusb presents it. A # header that lost one of these would still compile a trivial consumer and fail # every real one. hasline libusb1/hdr-enum-class 'enum libusb_class_code {' "$hf" hasline libusb1/hdr-enum-error 'enum libusb_error {' "$hf" hasline libusb1/hdr-enum-speed 'enum libusb_speed {' "$hf" hasline libusb1/hdr-enum-xfertype 'enum libusb_transfer_type {' "$hf" hasline libusb1/hdr-enum-desctype 'enum libusb_descriptor_type {' "$hf" hasline libusb1/hdr-struct-devdesc 'struct libusb_device_descriptor {' "$hf" hasline libusb1/hdr-struct-cfgdesc 'struct libusb_config_descriptor {' "$hf" hasline libusb1/hdr-struct-ifdesc 'struct libusb_interface_descriptor {' "$hf" hasline libusb1/hdr-struct-epdesc 'struct libusb_endpoint_descriptor {' "$hf" hasline libusb1/hdr-struct-setup 'struct libusb_control_setup {' "$hf" hasline libusb1/hdr-struct-xfer 'struct libusb_transfer {' "$hf" hasline libusb1/hdr-struct-version 'struct libusb_version {' "$hf" # Four enumerator VALUES, not just their names. -99 is the one this image will # actually see if anything ever calls libusb_init here (see the probe below), so # it is pinned with its number rather than left to the enum block above. hasline libusb1/hdr-val-success 'LIBUSB_SUCCESS = 0,' "$hf" hasline libusb1/hdr-val-nodevice 'LIBUSB_ERROR_NO_DEVICE = -4,' "$hf" hasline libusb1/hdr-val-notsup 'LIBUSB_ERROR_NOT_SUPPORTED = -12,' "$hf" hasline libusb1/hdr-val-other 'LIBUSB_ERROR_OTHER = -99' "$hf" hasline libusb1/hdr-val-perinterface 'LIBUSB_CLASS_PER_INTERFACE = 0x00,' "$hf" hasline libusb1/hdr-val-speedunknown 'LIBUSB_SPEED_UNKNOWN = 0,' "$hf" hasline libusb1/hdr-val-xfercontrol 'LIBUSB_TRANSFER_TYPE_CONTROL = 0U,' "$hf" # Hotplug is compiled in on Linux regardless of the udev choice -- without udev # the notifications arrive over NETLINK_KOBJECT_UEVENT instead. Asserting the # header still publishes the interface is what makes the linux_netlink.c case # below mean something. hasline libusb1/hdr-hotplug-arrived 'LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED = (1 << 0),' "$hf" hasline libusb1/hdr-hotplug-matchany '#define LIBUSB_HOTPLUG_MATCH_ANY -1' "$hf" # --- pkg-config ---------------------------------------------------------------- pct=$(cat "$pc" 2>/dev/null) has libusb1/pc-name 'Name: libusb-1.0' "$pct" has libusb1/pc-version 'Version: 1.0.27' "$pct" has libusb1/pc-libs 'Libs: -L${libdir} -lusb-1.0' "$pct" has libusb1/pc-cflags 'Cflags: -I${includedir}/libusb-1.0' "$pct" # libdir= must name /lib, matching where the archive actually is. This is the # line that makes the split placement above workable for a consumer. has libusb1/pc-libdir 'libdir=/lib' "$pct" # Libs.private is what a STATIC consumer must add, and on this arm it must be # empty: glibc 2.34+ folded libpthread and librt into libc, and udev is out. The # same source configured --enable-udev writes "Libs.private: -ludev" (measured # natively), which nothing on this image could satisfy -- so an empty value here # is the port decision seen from the consumer's side. # The shipped line is "Libs.private: " -- colon, ONE trailing space, newline -- # so the value has to be trimmed at BOTH ends before it is tested for emptiness. # Command substitution strips trailing newlines and nothing else, and a bare # [ -z ] against " " would have reported a red on a correct file. priv=$(sed -n 's/^Libs\.private://p' "$pc" 2>/dev/null | tr -d ' \t') if [ -z "$priv" ]; then bst_ok libusb1/pc-private-empty else bst_notok libusb1/pc-private-empty "Libs.private is not empty: '$priv'"; fi case "$pct" in *-ludev*) bst_notok libusb1/pc-no-udev "the .pc asks a consumer for -ludev" ;; *) bst_ok libusb1/pc-no-udev ;; esac # --- the archive --------------------------------------------------------------- check_eq libusb1/archive-format '!' "$(head -c 7 "$lib" 2>/dev/null)" # Flatten the archive's printable runs to one token per line: a raw grep over # binary data reports "Binary file matches" instead of the matches, which would # silently under-count and read as a partial compile. # # The set is written as the octal range rather than [:print:] because that is the # formulation both greps and both trs agree on -- 69-libb64.test derived it # empirically and the corpus has reused it since. `tr` here IS busybox's applet, # not coreutils': /usr/bin/tr is an update-alternatives link to tr.coreutils, but # busybox registers a tr applet and ash resolves applets ahead of $PATH. Both # forms were measured to work on this image (99 pass, 0 fail either way); the # octal one is used because it is the portable one, and because a flatten that # silently produced nothing would take every archive assertion below with it. flat=$tmp/libusb1-flat.$$ ( tr -c '\40-\176\n' '\n' < "$lib" > "$flat" 2>/dev/null ) # WHICH TRANSLATION UNITS ARE IN THE ARCHIVE, by each one's recorded source # path. Ten are expected; linux_udev.c is the eleventh that must NOT be, and # linux_netlink.c is the one that replaces it. tuf=$tmp/libusb1-tu.$$ printf '%s\n' \ 'libusb/core.c' 'libusb/descriptor.c' 'libusb/hotplug.c' 'libusb/io.c' \ 'libusb/strerror.c' 'libusb/sync.c' \ 'libusb/os/threads_posix.c' 'libusb/os/events_posix.c' \ 'libusb/os/linux_usbfs.c' 'libusb/os/linux_netlink.c' \ 'libusb/os/linux_udev.c' \ 'libusb1/1.0.27' \ > "$tuf" # -oF without -w here: these needles carry '/' and '.', which are not word # characters, so -w would demand boundaries the surrounding path does not have. # None of them is a substring of another, so plain -F is exact enough. tufound=$(grep -oFf "$tuf" "$flat" 2>/dev/null | sort -u) for f in core descriptor hotplug io strerror sync; do hasline "libusb1/tu-$f" "libusb/$f.c" "$tufound" done for f in threads_posix events_posix linux_usbfs linux_netlink; do hasline "libusb1/tu-$f" "libusb/os/$f.c" "$tufound" done noline libusb1/tu-no-linux-udev 'libusb/os/linux_udev.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 libusb1/lib-version-path 'libusb1/1.0.27' "$tufound" # --- completeness: every declared entry point is in the archive ----------------- # # LIBUSB_CALL marks every public entry point and LIBUSB_CALLV the one varargs # entry point (libusb_set_option). Requiring '(' after the name keeps this to # functions and excludes the callback TYPEDEFs, which spell it # `(LIBUSB_CALL *libusb_transfer_cb_fn)(` and so are followed by ')'. namef=$tmp/libusb1-names.$$ grep -oE 'LIBUSB_CALLV?[[:space:]]+\**[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(' "$inc" 2>/dev/null \ | sed -E 's/^LIBUSB_CALLV?[[:space:]]+\**//; s/[[:space:]]*\(.*$//' \ | sort -u > "$namef" # One batched, WORD-BOUNDED lookup: see the header comment for why -w is not # optional here. found=$(grep -owFf "$namef" "$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 silently matched nothing, would otherwise pass vacuously with # zero names to look up. 1.0.27 declares 97. if [ "$ndecl" -ge 90 ] && [ "$nfound" -eq "$ndecl" ]; then bst_ok libusb1/sym-complete else bst_notok libusb1/sym-complete "declared=$ndecl found=$nfound"; 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 libusb_init libusb_init_context libusb_exit libusb_set_option \ libusb_get_device_list libusb_free_device_list libusb_get_device_descriptor \ libusb_get_config_descriptor libusb_free_config_descriptor \ libusb_open libusb_close libusb_open_device_with_vid_pid \ libusb_claim_interface libusb_release_interface libusb_set_configuration \ libusb_control_transfer libusb_bulk_transfer libusb_interrupt_transfer \ libusb_alloc_transfer libusb_submit_transfer libusb_cancel_transfer \ libusb_free_transfer libusb_handle_events libusb_handle_events_timeout \ libusb_error_name libusb_strerror libusb_get_version \ libusb_hotplug_register_callback libusb_hotplug_deregister_callback \ libusb_kernel_driver_active libusb_detach_kernel_driver libusb_get_bus_number; do hasline "libusb1/sym-$s" "$s" "$found" done # NO LIBUDEV SYMBOL ANYWHERE. linux_udev.c is the only caller of these, so their # absence is the same fact as tu-no-linux-udev seen from the other side -- and it # is the one that would catch a future bbappend that kept the file but stubbed it. udevf=$tmp/libusb1-udev.$$ printf '%s\n' udev_new udev_unref udev_monitor_new_from_netlink \ udev_device_get_devnode udev_enumerate_new udev_monitor_receive_device > "$udevf" ufound=$(grep -owFf "$udevf" "$flat" 2>/dev/null | sort -u) for s in udev_new udev_unref udev_monitor_new_from_netlink udev_device_get_devnode \ udev_enumerate_new udev_monitor_receive_device; do noline "libusb1/no-$s" "$s" "$ufound" done rm -f "$hn" "$namef" "$tuf" "$udevf" # --- static-only guards --------------------------------------------------------- # # The distro appends --disable-shared at class-target scope, so a .so or .la here # would mean a recipe reached configure by some other route. There is no dynamic # loader on this arm, so such a file could not be used even if it linked. set -- /lib/libusb-1.0.so*; nofile libusb1/no-shared-base "$@" set -- /usr/lib/libusb-1.0.so*; nofile libusb1/no-shared-usr "$@" set -- /lib/libusb-1.0.la; nofile libusb1/no-libtool "$@" # examples/ builds listdevs, xusb, fxload, hotplugtest, sam3u_benchmark and # dpfp; upstream's make install ships none of them, and nothing here should. for b in listdevs xusb fxload hotplugtest sam3u_benchmark dpfp; do nofile "libusb1/no-example-$b" "/usr/bin/$b" done # --- the runtime probe ---------------------------------------------------------- # # THE ONE THING THE SHIPPED ARTIFACT CANNOT ANSWER is whether libusb_init # succeeds against a real bus. That needs a bus AND a consumer, and this image # has neither. The probe below establishes which of the two is missing and says # so in the skip reason, rather than asserting an environment fact that would go # red the day the runtime grows a host controller -- a machine with USB is a # HEALTHIER machine, and a suite that reds on it is wrong. devtmpfs=absent; [ -d /dev/bus/usb ] && devtmpfs=present procusb=absent; [ -d /proc/bus/usb ] && procusb=present usbcore=absent; [ -d /sys/bus/usb ] && usbcore=present ndev=0 if [ -d /sys/bus/usb/devices ]; then for d in /sys/bus/usb/devices/*; do [ -e "$d" ] && ndev=$((ndev + 1)); done fi probe="usbfs:/dev/bus/usb=$devtmpfs /proc/bus/usb=$procusb sysfs:/sys/bus/usb=$usbcore devices=$ndev" if [ "$devtmpfs" = absent ] && [ "$procusb" = absent ]; then # find_usbfs_path() returns NULL in this state (--disable-udev compiles out the # assume-/dev/bus/usb fallback), so op_init() would log "could not find usbfs" # and libusb_init would return LIBUSB_ERROR_OTHER (-99). Measured on the # identical source built natively, with /dev/bus and /sys/bus/usb hidden. bst_skip libusb1/init-against-bus \ "no USB device tree, so libusb_init would refuse -99 rather than succeed -- $probe" bst_skip libusb1/enumerate-against-bus \ "no USB device tree to enumerate -- $probe" else bst_skip libusb1/init-against-bus \ "a usbfs tree exists but this image installs no libusb consumer and has no C compiler, so libusb_init cannot be called here -- $probe" bst_skip libusb1/enumerate-against-bus \ "a usbfs tree exists but nothing on this image links libusb -- $probe" fi rm -f "$flat"