Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libzypp for openSUSE:Factory checked in at 2026-03-19 17:32:26 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libzypp (Old) and /work/SRC/openSUSE:Factory/.libzypp.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libzypp" Thu Mar 19 17:32:26 2026 rev:528 rq:1341004 version:17.38.5 Changes: -------- --- /work/SRC/openSUSE:Factory/libzypp/libzypp.changes 2026-03-12 22:21:58.997555487 +0100 +++ /work/SRC/openSUSE:Factory/.libzypp.new.8177/libzypp.changes 2026-03-19 17:33:40.440602285 +0100 @@ -1,0 +2,8 @@ +Wed Mar 18 14:36:27 CET 2026 - [email protected] + +- Fix preloader not caching packages from arch specific subrepos + (bsc#1253740) +- Deprioritize invalid mirrors (fixes openSUSE/zypper#636) +- version 17.38.5 (35) + +------------------------------------------------------------------- Old: ---- libzypp-17.38.4.tar.bz2 New: ---- libzypp-17.38.5.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libzypp.spec ++++++ --- /var/tmp/diff_new_pack.PkVlVO/_old 2026-03-19 17:33:43.548731161 +0100 +++ /var/tmp/diff_new_pack.PkVlVO/_new 2026-03-19 17:33:43.548731161 +0100 @@ -98,7 +98,7 @@ %endif Name: libzypp -Version: 17.38.4 +Version: 17.38.5 Release: 0 License: GPL-2.0-or-later URL: https://github.com/openSUSE/libzypp ++++++ libzypp-17.38.4.tar.bz2 -> libzypp-17.38.5.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/po/it.po new/libzypp-17.38.5/po/it.po --- old/libzypp-17.38.4/po/it.po 2026-01-11 16:37:36.000000000 +0100 +++ new/libzypp-17.38.5/po/it.po 2026-03-16 08:00:17.000000000 +0100 @@ -15,16 +15,16 @@ "Project-Id-Version: zypp\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-01-11 16:21+0100\n" -"PO-Revision-Date: 2025-07-22 15:01+0000\n" -"Last-Translator: Julia Faltenbacher <[email protected]>\n" -"Language-Team: Italian <https://l10n.opensuse.org/projects/libzypp/master/it/" -">\n" +"PO-Revision-Date: 2026-03-14 12:04+0000\n" +"Last-Translator: Paolo Za <[email protected]>\n" +"Language-Team: Italian <https://l10n.opensuse.org/projects/libzypp/master/" +"it/>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 5.16.2\n" "X-Poedit-Bookmarks: 370,-1,-1,-1,-1,-1,-1,-1,-1,-1\n" #. translators: an annotation to a gpg keys expiry date @@ -346,23 +346,23 @@ #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:254 msgid "No error" -msgstr "" +msgstr "Nessun errore" #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:256 msgid "Internal Error" -msgstr "" +msgstr "Errore interno" #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:258 msgid "The request was cancelled" -msgstr "" +msgstr "La richiesta è stata annullata" #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:260 msgid "The request exceeded the maximum download size" -msgstr "" +msgstr "La richiesta ha superato la dimensione massima per lo scaricamento" #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:262 msgid "The downloaded data did not result in a valid checksum" -msgstr "" +msgstr "I dati scaricati non hanno prodotto un codice di controllo valido" #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:264 msgid "The peer certificate could not be verified" @@ -370,35 +370,35 @@ #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:266 msgid "Connection failed" -msgstr "" +msgstr "Connessione non riuscita" #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:268 msgid "Unsupported protocol" -msgstr "" +msgstr "Protocollo non supportato" #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:270 msgid "Bad URL" -msgstr "" +msgstr "URL errato" #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:272 msgid "Requested location is temporarily unaccessible." -msgstr "" +msgstr "La posizione richiesta è temporaneamente inaccessibile." #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:274 msgid "Timeout reached" -msgstr "" +msgstr "Tempo massimo raggiunto" #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:276 msgid "Access to requested URL is forbidden." -msgstr "" +msgstr "L'accesso all'URL richiesto è vietato." #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:278 msgid "File not found" -msgstr "" +msgstr "File non trovato" #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:280 msgid "Authentication required but not provided." -msgstr "" +msgstr "Autenticazione richiesta ma non fornita." #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:282 msgid "Login failed." @@ -406,11 +406,11 @@ #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:284 msgid "Server returned an error for the given request." -msgstr "" +msgstr "Il server ha restituito un errore per la richiesta specificata." #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:286 msgid "Server did not send all requested ranges." -msgstr "" +msgstr "Il server non ha inviato tutti gli intervalli richiesti." #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:288 msgid "" @@ -420,7 +420,7 @@ #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:290 msgid "Server returned a HTTP/2 error." -msgstr "" +msgstr "Il server ha restituito un errore HTTP/2." #: zypp-logic/zypp-curl/ng/network/networkrequesterror.cc:292 msgid "Server returned a HTTP/2 stream error." @@ -5259,6 +5259,7 @@ #, boost-format msgid "Error: Failed to initialize transfer settings (%1%)." msgstr "" +"Errore: impossibile inizializzare le impostazioni di trasferimento (%1%)." #. if we have the file already, no need to download again #: zypp-logic/zypp/target/commitpackagepreloader.cc:143 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp/VERSION.cmake new/libzypp-17.38.5/zypp/VERSION.cmake --- old/libzypp-17.38.4/zypp/VERSION.cmake 2026-03-10 11:06:31.000000000 +0100 +++ new/libzypp-17.38.5/zypp/VERSION.cmake 2026-03-18 14:40:12.000000000 +0100 @@ -61,8 +61,8 @@ SET(LIBZYPP_MAJOR "17") SET(LIBZYPP_COMPATMINOR "35") SET(LIBZYPP_MINOR "38") -SET(LIBZYPP_PATCH "4") +SET(LIBZYPP_PATCH "5") # -# LAST RELEASED: 17.38.4 (35) +# LAST RELEASED: 17.38.5 (35) # (The number in parenthesis is LIBZYPP_COMPATMINOR) #======= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp/doc/autoinclude/Services.doc new/libzypp-17.38.5/zypp/doc/autoinclude/Services.doc --- old/libzypp-17.38.4/zypp/doc/autoinclude/Services.doc 2025-08-12 11:30:13.000000000 +0200 +++ new/libzypp-17.38.5/zypp/doc/autoinclude/Services.doc 2026-03-11 17:30:15.000000000 +0100 @@ -91,7 +91,7 @@ zypper refs \endverbatim -The repositories that are listed in the service will be added, using the reposotiy alias specified in the service index prefixed by the service alias: e.g. "myservice:myrepository". Repositories that vanished from the service will be automatically removed. +The repositories that are listed in the service will be added, using the repository alias specified in the service index prefixed by the service alias: e.g. "myservice:myrepository". Repositories that vanished from the service will be automatically removed. \section service-examples Example usecases diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp/package/libzypp.changes new/libzypp-17.38.5/zypp/package/libzypp.changes --- old/libzypp-17.38.4/zypp/package/libzypp.changes 2026-03-10 11:06:31.000000000 +0100 +++ new/libzypp-17.38.5/zypp/package/libzypp.changes 2026-03-18 14:40:12.000000000 +0100 @@ -1,4 +1,12 @@ ------------------------------------------------------------------- +Wed Mar 18 14:36:27 CET 2026 - [email protected] + +- Fix preloader not caching packages from arch specific subrepos + (bsc#1253740) +- Deprioritize invalid mirrors (fixes openSUSE/zypper#636) +- version 17.38.5 (35) + +------------------------------------------------------------------- Tue Mar 10 11:05:16 CET 2026 - [email protected] - Fix Product::referencePackage lookup (bsc#1259311) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp/tests/media/MediaCurl_test.cc new/libzypp-17.38.5/zypp/tests/media/MediaCurl_test.cc --- old/libzypp-17.38.4/zypp/tests/media/MediaCurl_test.cc 2025-11-17 16:10:13.000000000 +0100 +++ new/libzypp-17.38.5/zypp/tests/media/MediaCurl_test.cc 2026-03-16 11:30:12.000000000 +0100 @@ -8,6 +8,7 @@ #include <zypp/media/MediaManager.h> #include <zypp-core/Url.h> +#include <zypp-core/MirroredOrigin.h> #include <zypp/Digest.h> #include <iostream> @@ -501,3 +502,81 @@ BOOST_REQUIRE_NO_THROW(mm.provideFile( id, loc )); } +// case: verify that failed mirrors are deprioritized for subsequent requests +BOOST_DATA_TEST_CASE( test_mirror_fallback_persistence, bdata::make( backend ), backend ) +{ + // Setup ZConfig to avoid touching real system files + zypp::filesystem::TmpDir repoManagerRoot; + zypp::ZConfig::instance().setRepoManagerRoot( repoManagerRoot.path() ); + + // 1. Setup two servers on different ports + WebServer serverFail( "/tmp", 10001 ); + WebServer serverSuccess( "/tmp", 10002 ); + BOOST_REQUIRE( serverFail.start() ); + BOOST_REQUIRE( serverSuccess.start() ); + + int failServerRequests = 0; + int successServerRequests = 0; + + serverFail.setDefaultHandler( [&]( WebServer::Request &req ) { + failServerRequests++; + if ( req.params["REQUEST_URI"].find("file1") != std::string::npos ) + req.rout << "Status: 404 Not Found\r\n\r\n"; + else + req.rout << "Status: 200 OK\r\n\r\nSuccess from FailServer"; + }); + + serverSuccess.setDefaultHandler( [&]( WebServer::Request &req ) { + successServerRequests++; + req.rout << "Status: 200 OK\r\n\r\nSuccess from SuccessServer"; + }); + + // 2. Setup MirroredOrigin + // Authority: Use a non-existent port to force connection failure and fallback + zypp::Url authUrl("http://localhost:9999/authority"); + authUrl.setQueryParam("mediahandler", backend); + + zypp::MirroredOrigin origin( authUrl ); + + zypp::Url url1 = serverFail.url(); + url1.setQueryParam("mediahandler", backend); + origin.addMirror( url1 ); + + zypp::Url url2 = serverSuccess.url(); + url2.setQueryParam("mediahandler", backend); + origin.addMirror( url2 ); + + zypp::media::MediaManager mm; + zypp::media::MediaAccessId id; + id = mm.open( origin ); + mm.attach(id); + zypp_defer { + mm.releaseAll (); + mm.close (id); + }; + + // 3. First download: file1 + // Expected behavior: + // - mirrorOrder() returns [1, 2, 0] + // - Tries Mirror 1 (FailServer) -> returns 404 + // - Falls back to Mirror 2 (SuccessServer) -> returns 200 + zypp::OnMediaLocation loc1("/handler/file1"); + BOOST_CHECK_NO_THROW( mm.provideFile( id, loc1 ) ); + + BOOST_CHECK_EQUAL( failServerRequests, 1 ); + BOOST_CHECK_EQUAL( successServerRequests, 1 ); + + // 4. Second download: file2 + // DESIRED BEHAVIOR: + // After the first failure, Mirror 1 should have been moved to the end of the list. + // Thus, it should try Mirror 2 (serverSuccess) FIRST for file2. + zypp::OnMediaLocation loc2("/handler/file2"); + BOOST_CHECK_NO_THROW( mm.provideFile( id, loc2 ) ); + + // VERIFICATION: confirm that failed mirror was NOT tried again + // (We expect failServerRequests to remain 1) + BOOST_CHECK_MESSAGE( failServerRequests == 1, + "Failed mirror was NOT reordered! (failServerRequests=" << failServerRequests << ")" ); +} + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp/zypp/media/MediaCurl.cc new/libzypp-17.38.5/zypp/zypp/media/MediaCurl.cc --- old/libzypp-17.38.4/zypp/zypp/media/MediaCurl.cc 2026-03-04 14:40:10.000000000 +0100 +++ new/libzypp-17.38.5/zypp/zypp/media/MediaCurl.cc 2026-03-16 11:30:12.000000000 +0100 @@ -561,6 +561,9 @@ } catch (MediaException & excpt_r) { if ( !canTryNextMirror ( excpt_r ) ) ZYPP_RETHROW(excpt_r); + + that->deprioritizeMirror( mirr ); + lastErr = ZYPP_FWD_CURRENT_EXCPT(); } } @@ -851,6 +854,9 @@ } catch (MediaException & excpt_r) { if ( !canTryNextMirror ( excpt_r ) ) ZYPP_RETHROW(excpt_r); + + that->deprioritizeMirror( i ); + lastErr = ZYPP_FWD_CURRENT_EXCPT(); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp/zypp/media/MediaCurl2.cc new/libzypp-17.38.5/zypp/zypp/media/MediaCurl2.cc --- old/libzypp-17.38.4/zypp/zypp/media/MediaCurl2.cc 2025-09-01 15:40:22.000000000 +0200 +++ new/libzypp-17.38.5/zypp/zypp/media/MediaCurl2.cc 2026-03-16 11:30:12.000000000 +0100 @@ -265,6 +265,9 @@ } ZYPP_RETHROW(excpt_r); } + + this->deprioritizeMirror( mirr ); + ZYPP_CAUGHT(excpt_r); continue; } @@ -318,6 +321,9 @@ if( !canTryNextMirror ( e ) ) { break; } + + this->deprioritizeMirror( mirr ); + continue; } // exists diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp/zypp/media/MediaHandler.h new/libzypp-17.38.5/zypp/zypp/media/MediaHandler.h --- old/libzypp-17.38.4/zypp/zypp/media/MediaHandler.h 2025-09-01 15:40:22.000000000 +0200 +++ new/libzypp-17.38.5/zypp/zypp/media/MediaHandler.h 2026-03-16 11:30:12.000000000 +0100 @@ -497,6 +497,11 @@ std::string protocol() const { return _origin.scheme(); } /** + * Access the internal MirroredOrigin (authority and mirrors). + **/ + const MirroredOrigin &origin() const { return _origin; } + + /** * Primary Url used. **/ Url url() const { return _origin.authority().url(); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp/zypp/media/MediaNetworkCommonHandler.cc new/libzypp-17.38.5/zypp/zypp/media/MediaNetworkCommonHandler.cc --- old/libzypp-17.38.4/zypp/zypp/media/MediaNetworkCommonHandler.cc 2026-03-04 14:40:10.000000000 +0100 +++ new/libzypp-17.38.5/zypp/zypp/media/MediaNetworkCommonHandler.cc 2026-03-16 11:30:12.000000000 +0100 @@ -38,6 +38,11 @@ { _redirTargets.clear (); std::transform ( _origin.begin(), _origin.end(), std::back_inserter(_redirTargets), []( const OriginEndpoint &url_r ) { return findGeoIPRedirect(url_r.url()); } ); + + // initialize default mirror order: mirrors [1..N] then authority [0] + _mirrOrder.reserve( _origin.endpointCount() ); + for( unsigned i = 1; i < _origin.endpointCount () ; i++ ) { _mirrOrder.push_back(i); } + _mirrOrder.push_back(0); } void MediaNetworkCommonHandler::attachTo (bool next) @@ -322,16 +327,21 @@ std::vector<unsigned int> MediaNetworkCommonHandler::mirrorOrder(const OnMediaLocation &loc) const { - std::vector<unsigned> mirrOrder; if ( !loc.mirrorsAllowed () ) { MIL << "Fetching file " << loc << " from authority only: " << _origin << std::endl; - mirrOrder.push_back (0); // only authority - } else { - mirrOrder.reserve( _origin.endpointCount() ); - for( unsigned i = 1; i < _origin.endpointCount () ; i++ ) { mirrOrder.push_back(i) ;} - mirrOrder.push_back(0); // authority last + return { 0 }; // only authority + } + return _mirrOrder; + } + + void MediaNetworkCommonHandler::deprioritizeMirror( unsigned mirr ) const + { + auto it = std::find( _mirrOrder.begin(), _mirrOrder.end(), mirr ); + if ( it != _mirrOrder.end() && _mirrOrder.size() > 1 ) + { + // move found index to the end + std::rotate( it, it + 1, _mirrOrder.end() ); } - return mirrOrder; } bool MediaNetworkCommonHandler::authenticate( const Url &url, CredentialManager &cm, TransferSettings &settings, const std::string & availAuthTypes, bool firstTry ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp/zypp/media/MediaNetworkCommonHandler.h new/libzypp-17.38.5/zypp/zypp/media/MediaNetworkCommonHandler.h --- old/libzypp-17.38.4/zypp/zypp/media/MediaNetworkCommonHandler.h 2025-08-12 11:30:15.000000000 +0200 +++ new/libzypp-17.38.5/zypp/zypp/media/MediaNetworkCommonHandler.h 2026-03-16 11:30:12.000000000 +0100 @@ -88,6 +88,11 @@ std::vector<unsigned> mirrorOrder( const OnMediaLocation &loc ) const; + /** + * @brief Move mirror index @a mirr to the end of the attempt list. + */ + void deprioritizeMirror( unsigned mirr ) const; + public: // standard auth procedure, shared with CommitPackagePreloader @@ -114,6 +119,7 @@ protected: std::vector<Url> _redirTargets; + mutable std::vector<unsigned> _mirrOrder; }; } // namespace media diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp-logic/zypp/Fetcher.cc new/libzypp-17.38.5/zypp-logic/zypp/Fetcher.cc --- old/libzypp-17.38.4/zypp-logic/zypp/Fetcher.cc 2025-09-01 15:40:21.000000000 +0200 +++ new/libzypp-17.38.5/zypp-logic/zypp/Fetcher.cc 2026-03-18 14:40:12.000000000 +0100 @@ -35,6 +35,55 @@ namespace zypp { ///////////////////////////////////////////////////////////////// + namespace { + // Translate leading "./(../)+" to "./(%2E%2E/)+" for Fetcher::mapToCachePath + inline void fixup( Pathname & path_r ) + { + const std::string & s { path_r.asString() }; + + // 1. Quick exit if string is too short or doesn't start with ./ + if ( s.size() < 4 || s.compare(0, 2, "./" ) != 0 ) + return; + + size_t count = 0; + size_t pos = 2; + + // 2. Count consecutive "../" sequences + while ( pos + 3 <= s.size() && s.compare(pos, 3, "../") == 0 ) { + ++count; + pos += 3; + } + + if (count == 0) + return; + + { + std::string s { path_r.asString() }; + + // 3. Resize string once to avoid multiple reallocations + size_t original_size = s.size(); + size_t added_space = count * 4; // ".."(2) becomes "%2E%2E"(6) + s.resize(original_size + added_space); + + // 4. Move the 'tail' of the string to the new end + size_t tail_start = 2 + (count * 3); + size_t tail_length = original_size - tail_start; + if (tail_length > 0) { + std::copy_backward(s.begin() + tail_start, s.begin() + original_size, s.end()); + } + + // 5. Fill the gap with %2E%2E/ + size_t write_pos = 2; + for (size_t i = 0; i < count; ++i) { + s.replace(write_pos, 7, "%2E%2E/"); + write_pos += 7; + } + + path_r = s; + } + } + } // namespace + /** * class that represents indexes which add metadata * to fetcher jobs and therefore need to be retrieved @@ -201,11 +250,12 @@ void getDirectoryContent( MediaSetAccess &media, const OnMediaLocation &resource, filesystem::DirContent &content ); /** - * Tries to locate the file represented by job by looking at - * the cache (matching checksum is mandatory). Returns the - * location of the cached file or an empty \ref Pathname. + * Tries to locate the file represented by the job by looking at + * it's final destination first and at then the caches (matching + * checksum is mandatory). Returns the location of the file found + * or an empty \ref Pathname. */ - ManagedFile locateInCache( const OnMediaLocation & resource_r, const Pathname & destDir_r ); + ManagedFile locateInCache( const OnMediaLocation & resource_r, const Pathname & destDir_r, const Pathname & inCachePath_r ); /** * Validates the provided file against its checkers. * \throws Exception @@ -354,29 +404,29 @@ } else { - ERR << "Not adding cache '" << cache_dir << "'. Path does not exists." << endl; + WAR << "Not adding cache '" << cache_dir << "'. Path does not exists." << endl; } } - ManagedFile Fetcher::Impl::locateInCache( const OnMediaLocation & resource_r, const Pathname & destDir_r ) + ManagedFile Fetcher::Impl::locateInCache( const OnMediaLocation & resource_r, const Pathname & destDir_r, const Pathname & inCachePath_r ) { // No checksum - no match if ( resource_r.checksum().empty() ) return {}; - // first check in the destination directory - ManagedFile cacheLocation(destDir_r / resource_r.filename()); + // first check in the final destination + ManagedFile cacheLocation { destDir_r / inCachePath_r }; if ( PathInfo(cacheLocation).isExist() && is_checksum( cacheLocation, resource_r.checksum() ) ) { + pMIL( "file", inCachePath_r, "found at", destDir_r ); return cacheLocation; } - MIL << "start fetcher with " << _caches.size() << " cache directories." << endl; for( const CacheInfo & cacheInfo : _caches ) { - cacheLocation = ManagedFile(cacheInfo._pathName / resource_r.filename()); + cacheLocation = ManagedFile( cacheInfo._pathName / inCachePath_r ); if ( PathInfo(cacheLocation).isExist() && is_checksum( cacheLocation, resource_r.checksum() ) ) { - MIL << "file " << resource_r.filename() << " found in cache " << cacheInfo._pathName << endl; + pMIL( "file", inCachePath_r, "found in cache", cacheInfo._pathName ); if ( cacheInfo._options & Fetcher::CleanFiles ) cacheLocation.setDispose( filesystem::unlink ); return cacheLocation; @@ -532,43 +582,44 @@ void Fetcher::Impl::provideToDest( MediaSetAccess & media_r, const Pathname & destDir_r , const FetcherJob_Ptr & jobp_r ) { - const OnMediaLocation & resource( jobp_r->location ); + const OnMediaLocation & resource { jobp_r->location }; + Pathname inCachePath { mapToCachePath( resource ) }; // Map remote filename to a local path below destDir_r. + Pathname finalDestination { destDir_r / inCachePath }; // The full destination path. try { scoped_ptr<MediaSetAccess::ReleaseFileGuard> releaseFileGuard; // will take care provided files get released + Pathname candidate; - // get cached file (by checksum) or provide from media - ManagedFile managedTmpFile = locateInCache( resource, destDir_r ); - - Pathname tmpFile = managedTmpFile; - if ( tmpFile.empty() ) - { - MIL << "Not found in cache, retrieving..." << endl; - tmpFile = media_r.provideFile( resource, resource.optional() ? MediaSetAccess::PROVIDE_NON_INTERACTIVE : MediaSetAccess::PROVIDE_DEFAULT ); + // Try to get cached file (by checksum) or provide it from media. + ManagedFile cachedFile = locateInCache( resource, destDir_r, inCachePath ); + if ( (*cachedFile).empty() ) { + pMIL( "file", inCachePath, "not found in cache, retrieving..." ); + candidate = media_r.provideFile( resource, resource.optional() ? MediaSetAccess::PROVIDE_NON_INTERACTIVE : MediaSetAccess::PROVIDE_DEFAULT ); releaseFileGuard.reset( new MediaSetAccess::ReleaseFileGuard( media_r, resource ) ); // release it when we leave the block + } else { + candidate = cachedFile; } - // The final destination: locateInCache also checks destFullPath! - // If we find a cache match (by checksum) at destFullPath, take - // care it gets deleted, in case the validation fails. - ManagedFile destFullPath( destDir_r / resource.filename() ); - if ( tmpFile == destFullPath ) - destFullPath.setDispose( filesystem::unlink ); + if ( candidate == finalDestination ) { + // If we have a match at destFullPath, take care + // it gets deleted, in case the validation fails. + cachedFile.setDispose( filesystem::unlink ); + } - // validate the file (throws if not valid) - validate( tmpFile, jobp_r->checkers ); + // Validate the file (throws if not valid) + validate( candidate, jobp_r->checkers ); // move it to the final destination - if ( tmpFile == destFullPath ) - destFullPath.resetDispose(); // keep it! + if ( candidate == finalDestination ) + cachedFile.resetDispose(); // keep it! else { - if ( assert_dir( destFullPath->dirname() ) != 0 ) - ZYPP_THROW( Exception( "Can't create " + destFullPath->dirname().asString() ) ); + if ( assert_dir( finalDestination.dirname() ) != 0 ) + ZYPP_THROW( Exception( str::sprint( "Can't create", finalDestination.dirname() ) ) ); - if ( filesystem::hardlinkCopy( tmpFile, destFullPath ) != 0 ) - ZYPP_THROW( Exception( "Can't hardlink/copy " + tmpFile.asString() + " to " + destDir_r.asString() ) ); + if ( filesystem::hardlinkCopy( candidate, finalDestination ) != 0 ) + ZYPP_THROW( Exception( str::sprint( "Can't hardlink/copy", candidate, "to", finalDestination ) ) ); } } catch ( Exception & excpt ) @@ -576,12 +627,12 @@ if ( resource.optional() ) { ZYPP_CAUGHT( excpt ); - WAR << "optional resource " << resource << " could not be transferred." << endl; + WAR << "Optional resource " << resource << " could not be transferred." << endl; return; } else { - excpt.remember( "Can't provide " + resource.filename().asString() ); + excpt.remember( str::sprint("Can't provide", resource.filename() ) ); ZYPP_RETHROW( excpt ); } } @@ -936,6 +987,12 @@ _pimpl->start(dest_dir, media, progress_receiver); } + Pathname Fetcher::mapToCachePath( Pathname remotePath_r ) + { + fixup( remotePath_r ); + return remotePath_r; + } + std::ostream & operator<<( std::ostream & str, const Fetcher & obj ) { return str << *obj._pimpl; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp-logic/zypp/Fetcher.h new/libzypp-17.38.5/zypp-logic/zypp/Fetcher.h --- old/libzypp-17.38.4/zypp-logic/zypp/Fetcher.h 2025-09-01 15:40:21.000000000 +0200 +++ new/libzypp-17.38.5/zypp-logic/zypp/Fetcher.h 2026-03-18 14:40:12.000000000 +0100 @@ -101,6 +101,14 @@ * present, we fall back looking for a SHA1SUMS file. The checksum * type (md5,sha1,sha256) is auto detected by looking at the cheksums * length. No need to somehow encode it in the filename. + * + * \note bsc#1253740: Replicating the file tree below the destination + * directory fails if filenames stored in an OnMediaLocation are relative + * and referring to "../". They will point to a location outside the cache. + * To fix the Fetcher writing to a location outside the destination directory + * all remote paths will be mapped to filenames below the destination directory. + * \ref mapToCachePath will return the location below the destination directory, + * where a remote resource will be located. */ class ZYPP_API Fetcher { @@ -352,6 +360,20 @@ MediaSetAccess &media, const ProgressData::ReceiverFnc & progress = ProgressData::ReceiverFnc() ); + public: + /** Map a resource filename to a local path below a destDir. + * bsc#1253740: To fix the Fetcher writing to a location outside + * the destination directory if remote paths have leading "../"s, + * this function maps a resource filename to it's location below + * the destination directory. + * \note It's an implementation detail, that in many cases the + * "../"s are encoded as "%2E%2E/"s. This may change in future + * version if necessary. + */ + static Pathname mapToCachePath( Pathname remotePath_r ); + static Pathname mapToCachePath( const OnMediaLocation & resource_r ) + { return mapToCachePath( resource_r.filename() ); } + private: /** Pointer to implementation */ RWCOW_pointer<Impl> _pimpl; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp-logic/zypp/Package.cc new/libzypp-17.38.5/zypp-logic/zypp/Package.cc --- old/libzypp-17.38.4/zypp-logic/zypp/Package.cc 2025-09-01 15:40:21.000000000 +0200 +++ new/libzypp-17.38.5/zypp-logic/zypp/Package.cc 2026-03-18 14:40:12.000000000 +0100 @@ -21,6 +21,7 @@ #include <zypp/target/rpm/RpmDb.h> #include <zypp/target/rpm/RpmHeader.h> #include <zypp/ui/Selectable.h> +#include <zypp/repo/RepoProvideFile.h> /////////////////////////////////////////////////////////////////// namespace zyppintern @@ -98,7 +99,7 @@ // here and from SrcPackage.cc Pathname cachedLocation( const OnMediaLocation & loc_r, const RepoInfo & repo_r ) { - PathInfo pi( repo_r.packagesPath() / repo_r.path() / loc_r.filename() ); + PathInfo pi( repo_r.packagesPath() / repo::RepoMediaAccess::mapToCachePath( repo_r, loc_r ) ); if ( ! pi.isExist() ) return Pathname(); // no file in cache @@ -110,7 +111,7 @@ return Pathname(); // same name but no checksum to verify // for local repos compare with the checksum in repo - if ( CheckSum( CheckSum::md5Type(), std::ifstream( (url.getPathName() / repo_r.path() / loc_r.filename()).c_str() ) ) + if ( CheckSum( CheckSum::md5Type(), std::ifstream( (url.getPathName() / repo::RepoMediaAccess::mapToCachePath( repo_r, loc_r )).c_str() ) ) != CheckSum( CheckSum::md5Type(), std::ifstream( pi.c_str() ) ) ) return Pathname(); // same name but wrong checksum } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp-logic/zypp/repo/RepoProvideFile.cc new/libzypp-17.38.5/zypp-logic/zypp/repo/RepoProvideFile.cc --- old/libzypp-17.38.4/zypp-logic/zypp/repo/RepoProvideFile.cc 2026-03-02 13:29:55.000000000 +0100 +++ new/libzypp-17.38.5/zypp-logic/zypp/repo/RepoProvideFile.cc 2026-03-18 14:40:12.000000000 +0100 @@ -270,7 +270,7 @@ const OnMediaLocation locWithPath( OnMediaLocation(loc_rx).prependPath( repo_r.path() ) ); MIL << locWithPath << endl; - // Arrange DownloadFileReportHack to recieve the source::DownloadFileReport + // Arrange DownloadFileReportHack to receive the source::DownloadFileReport // and redirect download progress triggers to call the ProvideFilePolicy // callback. DownloadFileReportHack dumb( std::bind( std::mem_fn(&ProvideFilePolicy::progress), std::ref(policy_r), _1 ) ); @@ -349,7 +349,8 @@ // They should actually return a ManagedFile(). But I don't know if there is // already code which requests optional files but relies on provideFile returning // a ManagedFile holding the path even if it does not exist. So I keep it this way. - ManagedFile ret( destinationDir + locWithPath.filename() ); + + ManagedFile ret { destinationDir / Fetcher::mapToCachePath( locWithPath ) }; if ( !repo_r.keepPackages() ) { ret.setDispose( filesystem::unlink ); @@ -381,6 +382,12 @@ return ManagedFile(); // not reached } + Pathname RepoMediaAccess::mapToCachePath( const RepoInfo & repo_r, const OnMediaLocation & resource_r ) + { + // While our implementation is based on the Fetcher: + return Fetcher::mapToCachePath( repo_r.path() / resource_r.filename() ); + } + ///////////////////////////////////////////////////////////////// } // namespace repo /////////////////////////////////////////////////////////////////// diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp-logic/zypp/repo/RepoProvideFile.h new/libzypp-17.38.5/zypp-logic/zypp/repo/RepoProvideFile.h --- old/libzypp-17.38.4/zypp-logic/zypp/repo/RepoProvideFile.h 2026-03-02 13:29:55.000000000 +0100 +++ new/libzypp-17.38.5/zypp-logic/zypp/repo/RepoProvideFile.h 2026-03-18 14:40:12.000000000 +0100 @@ -100,6 +100,28 @@ /** Get the current default \ref ProvideFilePolicy. */ const ProvideFilePolicy & defaultPolicy() const; + public: + /** Map a resource filename to a local path below a repositories cache. + * + * bsc#1253740: Resource filenames pointing to "../" may refer to locations + * outside the repositories root. That's actually not desired and does not + * work for mounted media, as they refer to locations outside the mount point. + * Unfortunately it works for http/ftp and is actually used by TumbleWeed. + * + * The remote path relative to the repositories root is usually composed as + * "repo.path / resource.filename". Replicating the remote file tree below + * the repositories cache directory fails if this remote path is relative + * and refers to "../". It will point to a location outside the cache. + * To fix this all remote paths will be mapped to filenames below the cache + * root. + * + * So the location of a package on disk should be computed as: + * \code + * repo.packagesPath() / RepoMediaAccess::mapToCachePath( repo, resource ) + * \endcode + */ + static Pathname mapToCachePath( const RepoInfo & repo_r, const OnMediaLocation & resource_r ); + private: class Impl; RW_pointer<Impl> _impl; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp-logic/zypp/target/CommitPackageCache.cc new/libzypp-17.38.5/zypp-logic/zypp/target/CommitPackageCache.cc --- old/libzypp-17.38.4/zypp-logic/zypp/target/CommitPackageCache.cc 2025-09-01 15:40:22.000000000 +0200 +++ new/libzypp-17.38.5/zypp-logic/zypp/target/CommitPackageCache.cc 2026-03-18 14:40:12.000000000 +0100 @@ -132,12 +132,7 @@ assert( _pimpl ); } - CommitPackageCache::CommitPackageCache( const Pathname & /*rootDir_r*/, - const PackageProvider & packageProvider_r ) - : CommitPackageCache( packageProvider_r ) - {} - - CommitPackageCache::~CommitPackageCache() + CommitPackageCache::~CommitPackageCache() {} void CommitPackageCache::setCommitList( std::vector<sat::Solvable> commitList_r ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp-logic/zypp/target/CommitPackageCache.h new/libzypp-17.38.5/zypp-logic/zypp/target/CommitPackageCache.h --- old/libzypp-17.38.4/zypp-logic/zypp/target/CommitPackageCache.h 2025-09-01 15:40:22.000000000 +0200 +++ new/libzypp-17.38.5/zypp-logic/zypp/target/CommitPackageCache.h 2026-03-18 14:40:12.000000000 +0100 @@ -66,11 +66,6 @@ /** Ctor */ CommitPackageCache(PackageProvider packageProvider_r = RepoProvidePackage() ); - /** \deprecated Legacy Ctor; Pathname rootDir_r is not used. - * The repositories RepoInfo::packagesPath defines the cache location. - */ - CommitPackageCache( const Pathname & /*rootDir_r*/, const PackageProvider & packageProvider_r = RepoProvidePackage() ) ZYPP_DEPRECATED; - /** Dtor */ ~CommitPackageCache(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libzypp-17.38.4/zypp-logic/zypp/target/commitpackagepreloader.cc new/libzypp-17.38.5/zypp-logic/zypp/target/commitpackagepreloader.cc --- old/libzypp-17.38.4/zypp-logic/zypp/target/commitpackagepreloader.cc 2025-09-25 13:10:10.000000000 +0200 +++ new/libzypp-17.38.5/zypp-logic/zypp/target/commitpackagepreloader.cc 2026-03-18 14:40:12.000000000 +0100 @@ -11,6 +11,7 @@ #include <zypp-curl/transfersettings.h> #include <zypp-curl/ng/network/networkrequestdispatcher.h> #include <zypp-curl/ng/network/request.h> +#include <zypp/repo/RepoProvideFile.h> #include <zypp/MediaSetAccess.h> #include <zypp/Package.h> #include <zypp/SrcPackage.h> @@ -97,11 +98,7 @@ auto loc = _job.lookupLocation(); const auto repoInfo = _job.repoInfo(); - _targetPath = repoInfo.predownloadPath(); - if ( !repoInfo.path().emptyOrRoot () ) { - _targetPath /= repoInfo.path(); - } - _targetPath /= loc.filename(); + _targetPath = repoInfo.predownloadPath() / repo::RepoMediaAccess::mapToCachePath( repoInfo, loc ); // select a mirror we want to use if ( !prepareMirror( ) ) {
