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( ) ) {

Reply via email to