From 48893a012d96f1a9e4bd68df845cf6e01279844d Mon Sep 17 00:00:00 2001 From: Ramakrishnap <42624703+rgsl888prabhu@users.noreply.github.com> Date: Tue, 26 May 2026 22:56:26 +0530 Subject: [PATCH] io: dlopen libbz2 by versioned SONAME, not unversioned symlink (#1297) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Issue The release/26.06 container nightly CLI test fails on the `.lp.bz2` case: ``` ----------------- CLI TEST START --------------- Expected solution not found for .lp.bz2 Error: Process completed with exit code 1. ``` (e.g. https://github.com/NVIDIA/cuopt/actions/runs/26435964807/job/77827519723#step:5:1291) ## Root cause `cpp/src/io/file_to_string.cpp` dlopens libbz2 by its **unversioned** name: ```cpp dlopen("libbz2.so", RTLD_LAZY) ``` On Debian/Ubuntu, the unversioned `libbz2.so` symlink only ships with the **`libbz2-dev`** package. The runtime package (`libbz2-1.0`, pulled in transitively by `bzip2`) provides only `libbz2.so.1.0` / `libbz2.so.1`. So `dlopen` returns null, the .bz2 read aborts with *"Could not open .bz2 file..."*, and the CLI test grep miss surfaces as *"Expected solution not found for .lp.bz2"*. `.lp.gz` works in the same container because the zlib code path already uses the **versioned SONAME**: `dlopen("libz.so.1", RTLD_LAZY)`. ## Fix Mirror the zlib pattern. Try names in decreasing specificity and use the first that loads: 1. `libbz2.so.1.0` — bzip2 upstream's stable SONAME (`objdump -p` confirms; unchanged across all 1.0.x releases for ~20 years) 2. `libbz2.so.1` — symlink commonly shipped alongside .so.1.0 3. `libbz2.so` — last resort, dev-only symlink A future libbz2 2.x with SONAME `libbz2.so.2.0` would (correctly) fail to load — it would be an ABI break anyway, same as the existing zlib behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Authors: - Ramakrishnap (https://github.com/rgsl888prabhu) Approvers: - Hugo Linsenmaier (https://github.com/hlinsen) URL: https://github.com/NVIDIA/cuopt/pull/1297 --- cpp/src/io/file_to_string.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cpp/src/io/file_to_string.cpp b/cpp/src/io/file_to_string.cpp index f910eb977..77b92d90e 100644 --- a/cpp/src/io/file_to_string.cpp +++ b/cpp/src/io/file_to_string.cpp @@ -66,11 +66,20 @@ std::vector bz2_file_to_string(const std::string& file) BZ2_bzReadClose_t fptr = nullptr; }; - std::unique_ptr lbz2handle{dlopen("libbz2.so", RTLD_LAZY)}; + // bzip2 upstream's stable SONAME is "libbz2.so.1.0". On most distros only the + // versioned name(s) ship in the runtime package; the unversioned "libbz2.so" + // symlink is dev-only. Try names in decreasing specificity. + void* raw_lbz2handle = nullptr; + for (const char* soname : {"libbz2.so.1.0", "libbz2.so.1", "libbz2.so"}) { + raw_lbz2handle = dlopen(soname, RTLD_LAZY); + if (raw_lbz2handle != nullptr) break; + } + std::unique_ptr lbz2handle{raw_lbz2handle}; mps_parser_expects( lbz2handle != nullptr, error_type_t::ValidationError, - "Could not open .bz2 file since libbz2.so was not found. In order to open .bz2 files " + "Could not open .bz2 file since libbz2 was not found " + "(tried libbz2.so.1.0, libbz2.so.1, libbz2.so). In order to open .bz2 files " "directly, please ensure libbzip2 is installed. Alternatively, decompress the .bz2 file " "manually and open the uncompressed file. Given path: %s", file.c_str());