Conversation
…ibraries AX_REALPATH_LIB resolves a library to the fully versioned file present on the build system, e.g. /usr/lib/libusb-1.0.so.0.6.0, and that is the name nut-scanner remembers as SOPATH_LIB* and SOFILE_LIB* to dlopen() at run time. That file name can change on any upstream release of the library, including one that keeps the ABI and with it the SONAME (libusb-1.0.so.0). Add AX_SONAME_LIB which reads the SONAME recorded in the shared object (readelf or eu-readelf, objdump, elfdump on Solaris/illumos, otool -D on macOS, nothing on mingw where a DLL is loaded by its own file name) and call it from every nut_check_lib*.m4 that already uses AX_REALPATH_LIB, defining SONAME_LIB* next to the existing SOPATH_LIB* and SOFILE_LIB* values. A missing tool leaves SONAME_LIB* undefined. Signed-off-by: Jordan Rodgers <com6056@gmail.com>
nut-scanner only knew the file names it saw at build time, such as libusb-1.0.so.0.6.0 and the -dev link libusb-1.0.so, plus a few hardcoded variants. The SONAME libusb-1.0.so.0, the one name that stays valid across ABI-compatible updates and the only versioned name a runtime-only package installs, was not in the list. When Fedora 43 and Alpine 3.24 moved libusb from 1.0.29 to 1.0.30 (file renamed from .so.0.5.0 to .so.0.6.0) nut-scanner reported "Cannot load USB library (libusb-1.0.so.0.5.0)" until the NUT package was rebuilt, while the drivers, which link libusb-1.0.so.0 normally, kept working. Try SONAME_LIB* first in both the get_libname() pass and the lt_dlopen() fallback pass for every library that provides one (libusb 0.1 and 1.0, Net-SNMP, neon, Avahi, GLib/GIO, FreeIPMI). neon and Net-SNMP had the same latent problem (libneon.so.27.9.0, libnetsnmp.so.45.0.0). Also adds the words this introduces into docs prose to docs/nut.dict. See issue networkupstools#3474. Signed-off-by: Jordan Rodgers <com6056@gmail.com>
|
A ZIP file with standard source tarball and another tarball with pre-built docs for commit 78b13ce is temporarily available: NUT-tarballs-PR-3654.zip. |
|
✅ Build nut 2.8.5.5328-master completed (commit 453ab10bf0 by @com6056)
|
|
Great catch, investigation and all, thanks! |
…SONAME_LIB<X> is same as SOFILE_LIB<X> [networkupstools#3654] Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
…SONAME_LIB<X> is same as SOFILE_LIB<X> [networkupstools#3654] Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
70dbf1c to
97d61b1
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
|
✅ Build nut 2.8.5.5331-master completed (commit 0340885ce8 by @jimklimov)
|
|
Found one more thing while backporting this for Alpine edge. It's narrow, but I think it turns the bundled-installer caveat into a real regression rather than just an ordering question. When I put It takes a fairly specific layout to hit. Anything I tried the smallest fix on top of this branch, calling I can send that as a separate PR or add it here, whatever works better for you! |
|
Thanks, a good catch too! I think this can be fixed in a separate PR, I want to fix up complaints from clang21+ above in this one with benefits #3650 (it detects that I wonder if it makes sense to add something |
|
UPDATE: I see this PR is mostly unfinished in NUT CI farm, so I'd rather update the branch with pragmas against that warning and you can follow up in this PR or elsewhere. I'll handle the clean-up for master branch later. |
…code" for strcmp(SONAME_LIBX, SOFILE_LIBX) [networkupstools#3654] Newer builders (notably those with clang-21 and clang-22) on some agents correctly identify that we compare identical (macro) strings and the clause would never run. Ideally the comparison would be pre-processor equality check for strings, but as that is missing (or not portable), we compare to avoid work in vain at run-time - and now quiesce the compiler warnings about that on systems that do benefit from this check. To be revised after PR networkupstools#3650 is merged, to simplify these pragma blocks. Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
|
✅ Build nut 2.8.5.5335-master completed (commit 83dbf63944 by @jimklimov)
|
…ed SONAME_LIB* attempt [networkupstools#3654] Each nutscan_load_*_library() caches a failure by setting dl_handle to (lt_dlhandle)1, and returns 0 for every later call until the module is unloaded. In the lt_dlopen() fallback branch, that made the SOFILE_LIB* candidate unreachable once the SONAME_LIB* attempt before it failed, so a library reachable only under its versioned file name (a bundle with no SONAME symlink on the run-time linker search path) stopped being found. Unload the failed attempt first, which resets the handle without an extra lt_dlexit(), so the next candidate is really tried. Note that a library which is absent entirely now reports two failed candidates at debug level 0 instead of one, since the second name is really tried. Signed-off-by: Jordan Rodgers <com6056@gmail.com>
|
Nice, thanks for pushing the pragmas! I'll keep the general fix out of this one then. The SONAME-first reorder is what makes the caching bite though, so I pushed the narrow half here. It unloads the failed It deliberately stops there. Before this PR the fallback chain was Tested on Alpine edge on top of your pragma commit. With the versioned That test also shows your reporting point nicely. The debug run prints So yes to the helpers, and I'd go with the Two things to watch if you go that way. I'll do that together with the general retry reset in the follow-up PR, after your #3650 cleanup lands since it touches the same lines. Happy to split the reporting change out if you'd rather review it on its own. Disclosure as in the PR body, this commit was written with Claude Code (Anthropic) using Claude Opus 5, which made the change, ran the tests above and drafted this text, with review passes from Claude Opus 5 and Claude Fable 5.1 subagents. I reviewed all of it. |
|
✅ Build nut 2.8.5.5338-master completed (commit a7979338f1 by @com6056)
|
|
Shouldn't these unloads also happen in further fallbacks ( |
…der files [networkupstools#3654, networkupstools#3650] Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
|
✅ Build nut 2.8.5.5352-master completed (commit a0e9de4ebf by @jimklimov)
|
|
✅ Build nut 2.8.5.5353-master completed (commit 0410bd322c by @jimklimov)
|
nut-scannerloads libusb, Net-SNMP, neon and the rest withlt_dlopen()from a short list of names fixed at build time, and that list is missing the one name that is actually stable: the library's SONAME. This is #3474 (Fedora 43) and the same thing just happened on Alpine 3.24 (https://gitlab.alpinelinux.org/alpine/aports/-/work_items/18437). I maintainnutfor Alpine, so I went and looked at what the list really contains.For libusb 1.0 the candidates in
tools/nut-scanner/nutscan-init.careSOFILE_LIBUSB1(libusb-1.0.so.0.6.0on the build box),libusb-1.0.so,SOPATH_LIBUSB1(/usr/lib/libusb-1.0.so.0.6.0) andlibusb.so. The first and third come fromAX_REALPATH_LIBinm4/nut_check_libusb.m4:389, which follows the symlink chain to the real file, so they can change on any libusb release, including one that keeps the ABI.libusb-1.0.soonly exists with the-dev/-develpackage installed andlibusb.sois the 0.1 compat library.libusb-1.0.so.0, the name every driver in the same package is linked against and the only versioned name a runtime-only install has, is never tried. So a distro updating libusb 1.0.29 to 1.0.30 (.so.0.5.0to.so.0.6.0) leavesusbhid-upsworking andnut-scannersayingCannot load USB library (libusb-1.0.so.0.5.0)until NUT is rebuilt. neon (libneon.so.27.9.0) and Net-SNMP (libnetsnmp.so.45.0.0) are pinned the same way, their file names just haven't moved yet.What the change does:
m4/ax_realpath_lib.m4getsAX_SONAME_LIB, which reads the SONAME recorded in the shared object thatAX_REALPATH_LIBresolved, usingreadelf/eu-readelf, thenobjdump, thenelfdumpon Solaris/illumos, andotool -Don macOS (base name of the install name). Nothing on mingw, a DLL is loaded by its own file name. If no tool is around it just leaves the value empty.nut_check_lib*.m4that already callsAX_REALPATH_LIB(libusb 0.1 and 1.0, Net-SNMP, neon, Avahi, GLib and GIO, FreeIPMI) calls it right after and definesSONAME_LIB*next to the existingSOPATH_LIB*/SOFILE_LIB*, only when non-empty.nutscan-init.ctriesSONAME_LIB*first in both theget_libname()pass and thelt_dlopen()fallback pass, for each of those libraries. The search order comment at the top of that block explains why.libupsclientis untouched, itsSOFILEalready comes from libtool'sdlnameand is the SONAME.NEWS.adocandUPGRADING.adocentries, the latter because packagers can stop rebuilding NUT for these updates, plus eight words indocs/nut.dict(SONAME,soname,readelf,objdump,otool,elfdump, andSOFILE/SOPATH, which the docs had not used in prose before).One behaviour change worth calling out rather than burying. The comment above that block says
SOPATHis tried last so a bundled installer can override throughLD_LIBRARY_PATH, andget_libname()searchesLD_LIBRARY_PATHbefore the system paths. A bundle that ships only a fully versioned file with no SONAME link, on a host that also has a system copy, would now get the system one where it previously got the bundled one. Anything libtool installed has the SONAME link so I think this is very narrow, but you wrote that comment and should get to weigh it.Tested on Alpine (musl, GCC 15, binutils
readelf), everything below in a memory-capped container.autoreconf -fion the 2.8.5 tarball takes 14 s at about 22 MB peak RSS, and./autogen.shon master takes 8 s at about 23 MB.configurereports:and
config.hgainsSONAME_LIBUSB1 "libusb-1.0.so.0",SONAME_LIBNETSNMP "libnetsnmp.so.45",SONAME_LIBNEON "libneon.so.27". Libraries that were not configured stay#undef, so those blocks compile out.For the behaviour, I renamed each versioned file to a fake newer one, repointed the SONAME symlink and removed the
-devlink, which is what a runtime-only box looks like after an ABI-compatible update. Same machine, same libraries, two binaries. Alpine's currentnut-2.8.5-r1:and the patched build on the same box:
(SNMP is skipped there only because no IP range was given. The debug output shows
libnetsnmp.so.45.1.0resolved and loaded.)with
-Dconfirming the route,get_libname() resolved '/usr/lib/libusb-1.0.so.0.7.0' for LibUSB, i.e. it found the SONAME symlink andrealpath()took it to the renamed file.Two checks I could not get a clean result from, in both cases because they already fail the same way without my patch on the same machine.
make spellcheckfails onNEWS.adocfor pristine master as well as for this branch, andmake distcheck-lightfails identically on pristine master, producing a 20 byte tarball after faking all 274 man pages, which I take to be a missing doc toolchain in my container rather than anything real. My own added prose contributes zero rejected words once the dictionary additions above are in, andUPGRADING.adocgoes from failing to passing with them. Worth a second opinion from someone whose environment gets those two green.The
otoolbranch is tested on macOS with Apple'sotoolagainst homebrew libusb 1.0.30 and glib 2.88.3.configurereportschecking for SONAME recorded in /opt/homebrew/Cellar/libusb/1.0.30/lib/libusb-1.0.0.dylib... libusb-1.0.0.dyliband definesSONAME_LIBUSB1,SONAME_LIBGLIBandSONAME_LIBGIOaccordingly. Worth noting that on macOS the base name of the install name is already whatSOFILE_LIB*holds, so the SONAME entry is a harmless duplicate there rather than a fix. That run did not get as far as a full build, for an unrelated--with-libltdlheader path problem on homebrew, so this is a configure-stage result only.I have no illumos box, so the
elfdumpbranch is written to the documented output format and remains untested. It falls back to leavingSONAME_LIB*undefined, which restores exactly the current behaviour.Fixes #3474.
Disclosure per the template: written with Claude Code (Anthropic), using Claude Fable 5.1 for the m4 macro, the C change and a first draft of this text, and Claude Opus 5 for the investigation beforehand and the review after. I reviewed all of it, and the testing described above is mine.