A library build is not finished when files appear under lib/. It is finished when a consumer can find the header, link the correct ABI, load the intended shared object, enumerate hardware, handle errors, and release every reference. libusb makes that journey pleasantly small—provided package ownership and USB permissions are kept explicit.
What libusb does
libusb is a user-space C library for portable access to USB devices across Linux, Windows, macOS, BSD-family systems, Haiku, Solaris, and WebUSB targets. It gives applications device discovery, descriptor access, control/bulk/interrupt/isochronous transfers, interface claiming, hotplug callbacks, and asynchronous I/O without exposing one application API per host operating system.
Kernel, permissions, and process boundaries
The kernel owns host-controller and USB device drivers. libusb communicates through the operating system’s user-space backend; it is not a replacement kernel driver.
Enumeration and descriptor reading may work when opening an interface does not. On Linux, udev permissions and a bound kernel driver commonly determine access.
Claiming an interface can conflict with a kernel driver. Detach only when the device design and application lifecycle justify it, and reattach/clean up responsibly.
A successful transfer also depends on endpoint direction/type, maximum packet size, timeout policy, device state, and protocol—not only vendor/product IDs.
Path A: install Ubuntu’s development package
sudo apt update
sudo apt install build-essential pkg-config libusb-1.0-0-devRisk level: caution. Review the command before running it.
What APT installs
libusb-1.0-0-devprovides the public header, linker metadata, and development symlink matching Ubuntu’s packaged runtime library.build-essentialsupplies the native compiler/build baseline;pkg-configresolves consumer flags fromlibusb-1.0.pc.APT owns these files and can update them consistently with the operating system. Review the proposed package changes before confirming.
The package version may trail upstream by design. That is often preferable for stable distro integration unless the application requires a newer API or fix.
Verify the packaged development interface
pkg-config --modversion libusb-1.0
pkg-config --cflags libusb-1.0
pkg-config --libs libusb-1.0
dpkg -L libusb-1.0-0-dev | grep -E 'libusb\.h|libusb-1\.0\.pc'Read the metadata rather than hard-coding paths
The pkg-config module name is
libusb-1.0, while the header is conventionally included as<libusb-1.0/libusb.h>.--cflagsreturns required compiler include flags and--libsreturns linker flags such as-lusb-1.0.dpkg -Lproves which files belong to Ubuntu’s development package. It does not prove which shared object a finished process loads.Build systems should query pkg-config or an appropriate imported target rather than assuming
/usr/includeand/usr/lib.
Path B: build a reviewed upstream release
Upstream’s officially supported build system is Autotools. Use an official release tarball when possible because upstream notes that its release .tar.bz assets contain generated configure, while GitHub-generated source archives are snapshots without it. The Git-tag workflow below intentionally runs autogen.sh.
Install source-build prerequisites
sudo apt update
sudo apt install git build-essential autoconf automake libtool pkg-config libudev-devRisk level: caution. Review the command before running it.
Why each build dependency exists
Autoconf, Automake, and Libtool generate/configure the portable upstream build from a Git checkout.
libudev-devsupports libusb’s normal Linux device-discovery integration.pkg-configis used both while configuring dependencies and later by consumer programs.Documentation/examples can have optional dependencies; read the selected release’s README and configure summary instead of installing unrelated packages blindly.
Clone a tag, not a moving branch
git clone --branch v1.0.30 --depth 1 https://github.com/libusb/libusb.git
cd libusb
git describe --tags --always
git status --shortPinning makes the build explainable
--branch v1.0.30selects a concrete reviewed release and--depth 1avoids downloading unrelated history. Substitute a newer stable tag only after reviewing upstream releases.git describerecords the source identity used by the build.An empty
git status --shortconfirms no local source changes before configuration.For supply-chain-sensitive builds, verify the release artifact/signature or commit through your organization’s trusted process; HTTPS and a tag name alone are not a complete provenance policy.
Configure an isolated install prefix
./autogen.sh --prefix="$PWD/_install"
make -j"$(nproc)"
make check
make installWhy the local prefix matters
autogen.shgenerates the Autotools files from a Git checkout and passes the prefix to configuration. Inspect its final feature summary.make -j"$(nproc)"parallelizes compilation across detected processors; reduce concurrency on memory-limited builders.make checkruns the tests available for that configuration. Hardware/backend coverage may remain limited, so passing tests are necessary evidence—not complete validation.make installwrites under the repository’s_installdirectory without root access, keeping the experiment separate from APT-managed files.
Inspect and activate the custom prefix
find _install -maxdepth 3 -type f -o -type l
export PKG_CONFIG_PATH="$PWD/_install/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
pkg-config --modversion libusb-1.0
pkg-config --variable=prefix libusb-1.0Keep build-time and runtime lookup separate
findshows the installed headers, libraries, symlinks, and metadata without assuming an exact library filename. Parentheses would be needed if adding more predicates/actions to a complexfindexpression.Prepending
PKG_CONFIG_PATHmakes the compiler query resolve the isolated.pcfile for this shell.The reported prefix should end in the checkout’s
_install; otherwise the system package is still winning.pkg-config controls compilation/link flags. The dynamic loader uses its own search rules at runtime; solve deployment with packaging, an intentional rpath, or administrator-managed loader configuration—not a permanent ad-hoc environment variable.
A complete libusb enumeration program
#include <inttypes.h>
#include <stdio.h>
#include <libusb-1.0/libusb.h>
int main(void) {
libusb_context *ctx = NULL;
libusb_device **devices = NULL;
int rc = libusb_init(&ctx);
if (rc < 0) {
fprintf(stderr, "libusb_init: %s\n", libusb_error_name(rc));
return 1;
}
ssize_t count = libusb_get_device_list(ctx, &devices);
if (count < 0) {
fprintf(stderr, "get_device_list: %s\n",
libusb_error_name((int)count));
libusb_exit(ctx);
return 1;
}
for (ssize_t i = 0; i < count; ++i) {
struct libusb_device_descriptor descriptor;
rc = libusb_get_device_descriptor(devices[i], &descriptor);
if (rc < 0) {
fprintf(stderr, "descriptor: %s\n", libusb_error_name(rc));
continue;
}
printf("bus %03u device %03u: %04" PRIx16 ":%04" PRIx16 "\n",
libusb_get_bus_number(devices[i]),
libusb_get_device_address(devices[i]),
descriptor.idVendor, descriptor.idProduct);
}
libusb_free_device_list(devices, 1);
libusb_exit(ctx);
return 0;
}Initialize libusb, enumerate descriptors, release the device list, and shut down the context.
The ownership rules are the real lesson
libusb_init(&ctx)creates an explicit library context and returns zero or a negativelibusb_errorcode.libusb_get_device_listreturns a signed count or a negative error and gives each listed device a reference.libusb_get_device_descriptorreads the standard descriptor without opening a device handle; the program continues past a per-device error.libusb_free_device_list(devices, 1)frees the list and unreferences its device objects. The finallibusb_exit(ctx)releases context resources on both success and error paths.
Compile, inspect, and run the test
cc -std=c11 -Wall -Wextra -Wpedantic \
list_usb.c -o list_usb \
$(pkg-config --cflags --libs libusb-1.0)
ldd ./list_usb | grep -E 'libusb|not found'
./list_usbThree different failures, three different layers
A missing header is a compiler/include-path problem. Confirm
pkg-config --cflagsand the selected prefix.An undefined reference is a link command/order problem. Place the source/object before library flags and inspect
pkg-config --libs.lddreportingnot foundis a runtime loader problem, even if compilation succeeded. For a temporary isolated-prefix test, use a scopedLD_LIBRARY_PATH; production should package/configure the loader deliberately.LIBUSB_ERROR_ACCESSafter later opening a device is a permissions/driver issue, not proof that the library was linked incorrectly.
Give a non-root application device access with udev
SUBSYSTEM=="usb", ATTR{idVendor}=="1234", ATTR{idProduct}=="5678", GROUP="plugdev", MODE="0660"Example narrow udev rule; replace identifiers and group with values approved for the target device.
Treat the rule as an access-control change
Replace the example IDs with values verified from
lsusb/descriptors. A vendor-only wildcard can grant access to unrelated devices.MODE="0660"grants owner/group read-write access without making the device world-writable.Choose an existing managed group and explicitly enroll only authorized users; group membership commonly takes effect at a new login session.
Rules are evaluated when a device is added. Reloading rules does not necessarily reapply ownership to an already-connected device; reconnect or trigger it deliberately and verify with
udevadm info/filesystem permissions.
Cross-platform realities
On Windows, a compatible user-space driver such as WinUSB may need to bind to the interface; changing a production device’s driver can break its normal application.
On macOS, entitlement, sandbox, interface ownership, and platform driver behavior differ from Linux udev.
On Android, applications normally use platform USB host APIs and permissions; native libusb integration depends on file descriptors/build design and is not equivalent to desktop root access.
Hotplug support and backend behavior vary by platform and libusb build options. Feature-test and exercise the exact target OS rather than assuming source portability guarantees operational parity.
Troubleshooting checklist
`autogen.sh: command not found` or macro errors: verify the Git checkout and Autotools prerequisites; official release tarballs already contain
configure.`libudev.h` missing: install the distro’s libudev development package or make an intentional documented backend choice.
Wrong libusb version: compare
pkg-config --variable=prefix, compile flags,ldd, and loader cache; do not trust PATH.Program works only with sudo: stop using root as the workaround; create a narrow udev rule and verify device ownership/group access.
Device is busy: identify the bound kernel/process owner and understand the protocol before detaching a driver or killing a service.
Examples fail to link: ensure they use the same build/prefix metadata as the library and avoid mixing source-tree and installed headers.
Static link fails: request
pkg-config --static --libs libusb-1.0and satisfy private dependencies/license/deployment requirements.
Related USB engineering guides
Use usbutils and lsusb on Ubuntu to inspect topology and descriptors.
For packet-level work, continue with capturing USB traffic using usbmon and Wireshark.
If you are diagnosing serial adapters, see finding USB serial devices on Linux.
Primary references
The upstream libusb repository defines supported platforms, source, and the official Autotools project.
Upstream libusb releases distinguish official release tarballs from GitHub-generated snapshots and publish current tags.
The official API reference explains device discovery and reference counting and the broader libusb 1.0 API.
Comments and corrections