Imported Upstream version 15.0.0
[platform/upstream/libzypp.git] / zypp / RepoManager.cc
index 6e587ea..d9ce8bf 100644 (file)
@@ -21,6 +21,7 @@
 #include "zypp/base/InputStream.h"
 #include "zypp/base/LogTools.h"
 #include "zypp/base/Gettext.h"
+#include "zypp/base/DefaultIntegral.h"
 #include "zypp/base/Function.h"
 #include "zypp/base/Regex.h"
 #include "zypp/PathInfo.h"
@@ -41,7 +42,6 @@
 #include "zypp/repo/ServiceRepos.h"
 #include "zypp/repo/yum/Downloader.h"
 #include "zypp/repo/susetags/Downloader.h"
-#include "zypp/parser/plaindir/RepoParser.h"
 #include "zypp/repo/PluginServices.h"
 
 #include "zypp/Target.h" // for Target::targetDistribution() for repo index services
@@ -149,7 +149,7 @@ namespace zypp
     /**
      * \short Simple callback to collect the results
      *
-     * Classes like RepoFileParser call the callback
+     * Classes like RepoFileReader call the callback
      * once per each repo in a file.
      *
      * Passing this functor as callback, you can collect
@@ -178,9 +178,9 @@ namespace zypp
             && repo.targetDistribution() != targetDistro)
         {
           MIL
-            << "Skipping repository meant for '" << targetDistro
+            << "Skipping repository meant for '" << repo.targetDistribution()
             << "' distribution (current distro is '"
-            << repo.targetDistribution() << "')." << endl;
+            << targetDistro << "')." << endl;
 
           return true;
         }
@@ -204,7 +204,7 @@ namespace zypp
       MIL << "repo file: " << file << endl;
       RepoCollector collector;
       parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
-      return collector.repos;
+      return std::move(collector.repos);
     }
 
     ////////////////////////////////////////////////////////////////////////////
@@ -221,23 +221,35 @@ namespace zypp
     {
       MIL << "directory " << dir << endl;
       std::list<RepoInfo> repos;
-      std::list<Pathname> entries;
-      if ( filesystem::readdir( entries, dir, false ) != 0 )
+      bool nonroot( geteuid() != 0 );
+      if ( nonroot && ! PathInfo(dir).userMayRX() )
       {
-       // TranslatorExplanation '%s' is a pathname
-       ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
+       JobReport::warning( formatNAC(_("Cannot read repo directory '%1%': Permission denied")) % dir );
       }
-
-      str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$");
-      for ( std::list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
+      else
       {
-       if (str::regex_match(it->extension(), allowedRepoExt))
+       std::list<Pathname> entries;
+       if ( filesystem::readdir( entries, dir, false ) != 0 )
        {
-         std::list<RepoInfo> tmp = repositories_in_file( *it );
-         repos.insert( repos.end(), tmp.begin(), tmp.end() );
+         // TranslatorExplanation '%s' is a pathname
+         ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
+       }
 
-         //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos));
-         //MIL << "ok" << endl;
+       str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$");
+       for ( std::list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
+       {
+         if ( str::regex_match(it->extension(), allowedRepoExt) )
+         {
+           if ( nonroot && ! PathInfo(*it).userMayR() )
+           {
+             JobReport::warning( formatNAC(_("Cannot read repo file '%1%': Permission denied")) % *it );
+           }
+           else
+           {
+             const std::list<RepoInfo> & tmp( repositories_in_file( *it ) );
+             repos.insert( repos.end(), tmp.begin(), tmp.end() );
+           }
+         }
        }
       }
       return repos;
@@ -248,7 +260,7 @@ namespace zypp
     inline void assert_alias( const RepoInfo & info )
     {
       if ( info.alias().empty() )
-       ZYPP_THROW( RepoNoAliasException() );
+       ZYPP_THROW( RepoNoAliasException( info ) );
       // bnc #473834. Maybe we can match the alias against a regex to define
       // and check for valid aliases
       if ( info.alias()[0] == '.')
@@ -259,7 +271,7 @@ namespace zypp
     inline void assert_alias( const ServiceInfo & info )
     {
       if ( info.alias().empty() )
-       ZYPP_THROW( ServiceNoAliasException() );
+       ZYPP_THROW( ServiceNoAliasException( info ) );
       // bnc #473834. Maybe we can match the alias against a regex to define
       // and check for valid aliases
       if ( info.alias()[0] == '.')
@@ -400,6 +412,21 @@ namespace zypp
     return ret;
   }
 
+  std:: ostream & operator<<( std::ostream & str, const RepoManagerOptions & obj )
+  {
+#define OUTS(X) str << "  " #X "\t" << obj.X << endl
+    str << "RepoManagerOptions (" << obj.rootDir << ") {" << endl;
+    OUTS( repoRawCachePath );
+    OUTS( repoSolvCachePath );
+    OUTS( repoPackagesCachePath );
+    OUTS( knownReposPath );
+    OUTS( knownServicesPath );
+    OUTS( pluginsPath );
+    str << "}" << endl;
+#undef OUTS
+    return str;
+  }
+
   ///////////////////////////////////////////////////////////////////
   /// \class RepoManager::Impl
   /// \brief RepoManager implementation.
@@ -415,19 +442,62 @@ namespace zypp
       init_knownRepositories();
     }
 
+    ~Impl()
+    {
+      // trigger appdata refresh if some repos change
+      if ( _reposDirty && geteuid() == 0 && ( _options.rootDir.empty() || _options.rootDir == "/" ) )
+      {
+       try {
+         std::list<Pathname> entries;
+         filesystem::readdir( entries, _options.pluginsPath/"appdata", false );
+         if ( ! entries.empty() )
+         {
+           ExternalProgram::Arguments cmd;
+           cmd.push_back( "<" );               // discard stdin
+           cmd.push_back( ">" );               // discard stdout
+           cmd.push_back( "PROGRAM" );         // [2] - fix index below if changing!
+           for ( const auto & rinfo : repos() )
+           {
+             if ( ! rinfo.enabled() )
+               continue;
+             cmd.push_back( "-R" );
+             cmd.push_back( rinfo.alias() );
+             cmd.push_back( "-t" );
+             cmd.push_back( rinfo.type().asString() );
+             cmd.push_back( "-p" );
+             cmd.push_back( rinfo.metadataPath().asString() );
+           }
+
+           for_( it, entries.begin(), entries.end() )
+           {
+             PathInfo pi( *it );
+             //DBG << "/tmp/xx ->" << pi << endl;
+             if ( pi.isFile() && pi.userMayRX() )
+             {
+               // trigger plugin
+               cmd[2] = pi.asString();         // [2] - PROGRAM
+               ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
+             }
+           }
+         }
+       }
+       catch (...) {}  // no throw in dtor
+      }
+    }
+
   public:
-    bool repoEmpty() const             { return _repos.empty(); }
-    RepoSizeType repoSize() const      { return _repos.size(); }
-    RepoConstIterator repoBegin() const        { return _repos.begin(); }
-    RepoConstIterator repoEnd() const  { return _repos.end(); }
+    bool repoEmpty() const             { return repos().empty(); }
+    RepoSizeType repoSize() const      { return repos().size(); }
+    RepoConstIterator repoBegin() const        { return repos().begin(); }
+    RepoConstIterator repoEnd() const  { return repos().end(); }
 
     bool hasRepo( const std::string & alias ) const
-    { return foundAliasIn( alias, _repos ); }
+    { return foundAliasIn( alias, repos() ); }
 
     RepoInfo getRepo( const std::string & alias ) const
     {
-      RepoConstIterator it( findAlias( alias, _repos ) );
-      return it == _repos.end() ? RepoInfo::noRepo : *it;
+      RepoConstIterator it( findAlias( alias, repos() ) );
+      return it == repos().end() ? RepoInfo::noRepo : *it;
     }
 
   public:
@@ -498,11 +568,11 @@ namespace zypp
     void removeService( const ServiceInfo & service )
     { removeService( service.alias() ); }
 
-    void refreshServices();
+    void refreshServices( const RefreshServiceOptions & options_r );
 
-    void refreshService( const std::string & alias );
-    void refreshService( const ServiceInfo & service )
-    {  refreshService( service.alias() ); }
+    void refreshService( const std::string & alias, const RefreshServiceOptions & options_r );
+    void refreshService( const ServiceInfo & service, const RefreshServiceOptions & options_r )
+    {  refreshService( service.alias(), options_r ); }
 
     void modifyService( const std::string & oldAlias, const ServiceInfo & newService );
 
@@ -532,8 +602,8 @@ namespace zypp
     void getRepositoriesInService( const std::string & alias, OutputIterator out ) const
     {
       MatchServiceAlias filter( alias );
-      std::copy( boost::make_filter_iterator( filter, _repos.begin(), _repos.end() ),
-                 boost::make_filter_iterator( filter, _repos.end(), _repos.end() ),
+      std::copy( boost::make_filter_iterator( filter, repos().begin(), repos().end() ),
+                 boost::make_filter_iterator( filter, repos().end(), repos().end() ),
                  out);
     }
 
@@ -541,11 +611,16 @@ namespace zypp
     void init_knownServices();
     void init_knownRepositories();
 
+    const RepoSet & repos() const { return _reposX; }
+    RepoSet & reposManip()        { if ( ! _reposDirty ) _reposDirty = true; return _reposX; }
+
   private:
     RepoManagerOptions _options;
-    RepoSet            _repos;
+    RepoSet            _reposX;
     ServiceSet         _services;
 
+    DefaultIntegral<bool,false> _reposDirty;
+
   private:
     friend Impl * rwcowClone<Impl>( const Impl * rhs );
     /** clone for RWCOW_pointer */
@@ -631,45 +706,99 @@ namespace zypp
     repo::PluginServices(_options.pluginsPath/"services", ServiceCollector(_services));
   }
 
+  ///////////////////////////////////////////////////////////////////
+  namespace {
+    /** Delete \a cachePath_r subdirs not matching known aliases in \a repoEscAliases_r (must be sorted!)
+     * \note bnc#891515: Auto-cleanup only zypp.conf default locations. Otherwise
+     * we'd need some magic file to identify zypp cache directories. Without this
+     * we may easily remove user data (zypper --pkg-cache-dir . download ...)
+     */
+    inline void cleanupNonRepoMetadtaFolders( const Pathname & cachePath_r,
+                                             const Pathname & defaultCachePath_r,
+                                             const std::list<std::string> & repoEscAliases_r )
+    {
+      if ( cachePath_r != defaultCachePath_r )
+       return;
+
+      std::list<std::string> entries;
+      if ( filesystem::readdir( entries, cachePath_r, false ) == 0 )
+      {
+       entries.sort();
+       std::set<std::string> oldfiles;
+       set_difference( entries.begin(), entries.end(), repoEscAliases_r.begin(), repoEscAliases_r.end(),
+                       std::inserter( oldfiles, oldfiles.end() ) );
+       for ( const std::string & old : oldfiles )
+       {
+         if ( old == Repository::systemRepoAlias() )   // don't remove the @System solv file
+           continue;
+         filesystem::recursive_rmdir( cachePath_r / old );
+       }
+      }
+    }
+  } // namespace
+  ///////////////////////////////////////////////////////////////////
   void RepoManager::Impl::init_knownRepositories()
   {
     MIL << "start construct known repos" << endl;
 
     if ( PathInfo(_options.knownReposPath).isExist() )
     {
-      RepoInfoList repol = repositories_in_dir(_options.knownReposPath);
-      std::list<string> repo_esc_aliases;
-      std::list<string> entries;
-      for ( RepoInfoList::iterator it = repol.begin();
-            it != repol.end();
-            ++it )
+      std::list<std::string> repoEscAliases;
+      std::list<RepoInfo> orphanedRepos;
+      for ( RepoInfo & repoInfo : repositories_in_dir(_options.knownReposPath) )
       {
         // set the metadata path for the repo
-        Pathname metadata_path = rawcache_path_for_repoinfo(_options, (*it));
-        (*it).setMetadataPath(metadata_path);
-
+        repoInfo.setMetadataPath( rawcache_path_for_repoinfo(_options, repoInfo) );
        // set the downloaded packages path for the repo
-       Pathname packages_path = packagescache_path_for_repoinfo(_options, (*it));
-       (*it).setPackagesPath(packages_path);
+       repoInfo.setPackagesPath( packagescache_path_for_repoinfo(_options, repoInfo) );
+       // remember it
+        _reposX.insert( repoInfo );    // direct access via _reposX in ctor! no reposManip.
+
+       // detect orphaned repos belonging to a deleted service
+       const std::string & serviceAlias( repoInfo.service() );
+       if ( ! ( serviceAlias.empty() || hasService( serviceAlias ) ) )
+       {
+         WAR << "Schedule orphaned service repo for deletion: " << repoInfo << endl;
+         orphanedRepos.push_back( repoInfo );
+         continue;     // don't remember it in repoEscAliases
+       }
 
-        _repos.insert(*it);
-        repo_esc_aliases.push_back(it->escaped_alias());
+        repoEscAliases.push_back(repoInfo.escaped_alias());
       }
 
-      // delete metadata folders without corresponding repo (e.g. old tmp directories)
-      if ( filesystem::readdir( entries, _options.repoRawCachePath, false ) == 0 )
+      // Cleanup orphanded service repos:
+      if ( ! orphanedRepos.empty() )
       {
-        std::set<string> oldfiles;
-        repo_esc_aliases.sort();
-        entries.sort();
-        set_difference(entries.begin(), entries.end(), repo_esc_aliases.begin(), repo_esc_aliases.end(), std::inserter(oldfiles, oldfiles.end()));
-        for_(it, oldfiles.begin(), oldfiles.end())
-        {
-          filesystem::recursive_rmdir(_options.repoRawCachePath / *it);
-        }
+       for ( auto & repoInfo : orphanedRepos )
+       {
+         MIL << "Delete orphaned service repo " << repoInfo.alias() << endl;
+         // translators: Cleanup a repository previously owned by a meanwhile unknown (deleted) service.
+         //   %1% = service name
+         //   %2% = repository name
+         JobReport::warning( formatNAC(_("Unknown service '%1%': Removing orphaned service repository '%2%'" ))
+                             % repoInfo.service()
+                             % repoInfo.alias() );
+         try {
+           removeRepository( repoInfo );
+         }
+         catch ( const Exception & caugth )
+         {
+           JobReport::error( caugth.asUserHistory() );
+         }
+       }
       }
-    }
 
+      // delete metadata folders without corresponding repo (e.g. old tmp directories)
+      //
+      // bnc#891515: Auto-cleanup only zypp.conf default locations. Otherwise
+      // we'd need somemagic file to identify zypp cache directories. Without this
+      // we may easily remove user data (zypper --pkg-cache-dir . download ...)
+      repoEscAliases.sort();
+      RepoManagerOptions defaultCache( _options.rootDir );
+      cleanupNonRepoMetadtaFolders( _options.repoRawCachePath,         defaultCache.repoRawCachePath,          repoEscAliases );
+      cleanupNonRepoMetadtaFolders( _options.repoSolvCachePath,                defaultCache.repoSolvCachePath,         repoEscAliases );
+      cleanupNonRepoMetadtaFolders( _options.repoPackagesCachePath,    defaultCache.repoPackagesCachePath,     repoEscAliases );
+    }
     MIL << "end construct known repos" << endl;
   }
 
@@ -679,39 +808,26 @@ namespace zypp
   {
     Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
     Pathname productdatapath = rawproductdata_path_for_repoinfo( _options, info );
-    RepoType repokind = info.type();
-    RepoStatus status;
 
-    switch ( repokind.toEnum() )
-    {
-      case RepoType::NONE_e:
-       // unknown, probe the local metadata
-       repokind = probe( productdatapath.asUrl() );
-      break;
-      default:
-      break;
-    }
+    RepoType repokind = info.type();
+    // If unknown, probe the local metadata
+    if ( repokind == RepoType::NONE )
+      repokind = probe( productdatapath.asUrl() );
 
+    RepoStatus status;
     switch ( repokind.toEnum() )
     {
       case RepoType::RPMMD_e :
-      {
-        status = RepoStatus( productdatapath + "/repodata/repomd.xml");
-      }
-      break;
+        status = RepoStatus( productdatapath/"repodata/repomd.xml");
+       break;
 
       case RepoType::YAST2_e :
-      {
-        status = RepoStatus( productdatapath + "/content") && (RepoStatus( mediarootpath + "/media.1/media"));
-      }
-      break;
+        status = RepoStatus( productdatapath/"content" ) && RepoStatus( mediarootpath/"media.1/media" );
+       break;
 
       case RepoType::RPMPLAINDIR_e :
-      {
-        if ( PathInfo(Pathname(productdatapath + "/cookie")).isExist() )
-          status = RepoStatus( productdatapath + "/cookie");
-      }
-      break;
+       status = RepoStatus::fromCookieFile( productdatapath/"cookie" );
+       break;
 
       case RepoType::NONE_e :
        // Return default RepoStatus in case of RepoType::NONE
@@ -763,18 +879,14 @@ namespace zypp
   RepoManager::RefreshCheckStatus RepoManager::Impl::checkIfToRefreshMetadata( const RepoInfo & info, const Url & url, RawMetadataRefreshPolicy policy )
   {
     assert_alias(info);
-
-    RepoStatus oldstatus;
-    RepoStatus newstatus;
-
     try
     {
       MIL << "Going to try to check whether refresh is needed for " << url << endl;
 
       // first check old (cached) metadata
       Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
-      filesystem::assert_dir(mediarootpath);
-      oldstatus = metadataStatus(info);
+      filesystem::assert_dir( mediarootpath );
+      RepoStatus oldstatus = metadataStatus( info );
 
       if ( oldstatus.empty() )
       {
@@ -783,12 +895,15 @@ namespace zypp
       }
 
       {
-        std::string scheme( url.getScheme() );
-        if ( scheme == "cd" || scheme == "dvd" )
-        {
-          MIL << "never refresh CD/DVD" << endl;
+        if ( url.schemeIsVolatile() )
+       {
+         MIL << "never refresh CD/DVD" << endl;
           return REPO_UP_TO_DATE;
-        }
+       }
+       if ( url.schemeIsLocal() )
+       {
+         policy = RefreshIfNeededIgnoreDelay;
+       }
       }
 
       // now we've got the old (cached) status, we can decide repo.refresh.delay
@@ -820,83 +935,61 @@ namespace zypp
         }
       }
 
-      // To test the new matadta create temp dir as sibling of mediarootpath
-      filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
-
       repo::RepoType repokind = info.type();
-      // if the type is unknown, try probing.
-      switch ( repokind.toEnum() )
-      {
-        case RepoType::NONE_e:
-          // unknown, probe it \todo respect productdir
-          repokind = probe( url, info.path() );
-        break;
-        default:
-        break;
-      }
+      // if unknown: probe it
+      if ( repokind == RepoType::NONE )
+       repokind = probe( url, info.path() );
 
-      if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
-           ( repokind.toEnum() == RepoType::YAST2_e ) )
+      // retrieve newstatus
+      RepoStatus newstatus;
+      switch ( repokind.toEnum() )
       {
-        MediaSetAccess media(url);
-        shared_ptr<repo::Downloader> downloader_ptr;
-
-        if ( repokind.toEnum() == RepoType::RPMMD_e )
-          downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
-        else
-          downloader_ptr.reset( new susetags::Downloader(info, mediarootpath));
+       case RepoType::RPMMD_e:
+       {
+         MediaSetAccess media( url );
+         newstatus = yum::Downloader( info, mediarootpath ).status( media );
+       }
+       break;
 
-        RepoStatus newstatus = downloader_ptr->status(media);
-        bool refresh = false;
-        if ( oldstatus.checksum() == newstatus.checksum() )
-        {
-          MIL << "repo has not changed" << endl;
-          if ( policy == RefreshForced )
-          {
-            MIL << "refresh set to forced" << endl;
-            refresh = true;
-          }
-        }
-        else
-        {
-          MIL << "repo has changed, going to refresh" << endl;
-          refresh = true;
-        }
+       case RepoType::YAST2_e:
+       {
+         MediaSetAccess media( url );
+         newstatus = susetags::Downloader( info, mediarootpath ).status( media );
+       }
+       break;
 
-        if (!refresh)
-          touchIndexFile(info);
+       case RepoType::RPMPLAINDIR_e:
+         newstatus = RepoStatus( MediaMounter(url).getPathName(info.path()) ); // dir status
+         break;
 
-        return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
+       default:
+       case RepoType::NONE_e:
+         ZYPP_THROW( RepoUnknownTypeException( info ) );
+         break;
       }
-      else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
-      {
-        MediaMounter media( url );
-        RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
-        bool refresh = false;
-        if ( oldstatus.checksum() == newstatus.checksum() )
-        {
-          MIL << "repo has not changed" << endl;
-          if ( policy == RefreshForced )
-          {
-            MIL << "refresh set to forced" << endl;
-            refresh = true;
-          }
-        }
-        else
-        {
-          MIL << "repo has changed, going to refresh" << endl;
-          refresh = true;
-        }
-
-        if (!refresh)
-          touchIndexFile(info);
 
-        return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
+      // check status
+      bool refresh = false;
+      if ( oldstatus == newstatus )
+      {
+       MIL << "repo has not changed" << endl;
+       if ( policy == RefreshForced )
+       {
+         MIL << "refresh set to forced" << endl;
+         refresh = true;
+       }
       }
       else
       {
-        ZYPP_THROW(RepoUnknownTypeException(info));
+       MIL << "repo has changed, going to refresh" << endl;
+       refresh = true;
       }
+
+      if (!refresh)
+       touchIndexFile(info);
+
+      return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
+
     }
     catch ( const Exception &e )
     {
@@ -915,9 +1008,12 @@ namespace zypp
     assert_urls(info);
 
     // we will throw this later if no URL checks out fine
-    RepoException rexception(_PL("Valid metadata not found at specified URL",
-                                 "Valid metadata not found at specified URLs",
-                                info.baseUrlsSize() ) );
+    RepoException rexception( info, _PL("Valid metadata not found at specified URL",
+                                       "Valid metadata not found at specified URLs",
+                                       info.baseUrlsSize() ) );
+
+    // Suppress (interactive) media::MediaChangeReport if we in have multiple basurls (>1)
+    media::ScopedDisableMediaChangeReport guard( info.baseUrlsSize() > 1 );
 
     // try urls one by one
     for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
@@ -936,32 +1032,28 @@ namespace zypp
         repo::RepoType repokind = info.type();
 
         // if the type is unknown, try probing.
-        switch ( repokind.toEnum() )
-        {
-          case RepoType::NONE_e:
-            // unknown, probe it
-            repokind = probe( *it, info.path() );
-
-            if (repokind.toEnum() != RepoType::NONE_e)
-            {
-              // Adjust the probed type in RepoInfo
-              info.setProbedType( repokind ); // lazy init!
-              //save probed type only for repos in system
-              for_( it, repoBegin(), repoEnd() )
-              {
-                if ( info.alias() == (*it).alias() )
-                {
-                  RepoInfo modifiedrepo = info;
-                  modifiedrepo.setType( repokind );
-                  modifyRepository( info.alias(), modifiedrepo );
-                  break;
-                }
-              }
-            }
-          break;
-          default:
-          break;
-        }
+       if ( repokind == RepoType::NONE )
+       {
+         // unknown, probe it
+         repokind = probe( *it, info.path() );
+
+         if (repokind.toEnum() != RepoType::NONE_e)
+         {
+           // Adjust the probed type in RepoInfo
+           info.setProbedType( repokind ); // lazy init!
+           //save probed type only for repos in system
+           for_( it, repoBegin(), repoEnd() )
+           {
+             if ( info.alias() == (*it).alias() )
+             {
+               RepoInfo modifiedrepo = info;
+               modifiedrepo.setType( repokind );
+               modifyRepository( info.alias(), modifiedrepo );
+               break;
+             }
+           }
+         }
+       }
 
         Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
         if( filesystem::assert_dir(mediarootpath) )
@@ -1009,32 +1101,21 @@ namespace zypp
         else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
         {
           MediaMounter media( url );
-          RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
+          RepoStatus newstatus = RepoStatus( media.getPathName( info.path() ) );       // dir status
 
           Pathname productpath( tmpdir.path() / info.path() );
           filesystem::assert_dir( productpath );
-          std::ofstream file( (productpath/"cookie").c_str() );
-          if ( !file )
-          {
-            // TranslatorExplanation '%s' is a filename
-            ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), (productpath/"cookie").c_str() )));
-          }
-          file << url;
-          if ( ! info.path().empty() && info.path() != "/" )
-            file << " (" << info.path() << ")";
-          file << endl;
-          file << newstatus.checksum() << endl;
-
-          file.close();
+         newstatus.saveToCookieFile( productpath/"cookie" );
         }
         else
         {
-          ZYPP_THROW(RepoUnknownTypeException());
+          ZYPP_THROW(RepoUnknownTypeException( info ));
         }
 
         // ok we have the metadata, now exchange
         // the contents
        filesystem::exchange( tmpdir.path(), mediarootpath );
+       reposManip();   // remember to trigger appdata refresh
 
         // we are done.
         return;
@@ -1104,7 +1185,7 @@ namespace zypp
       MIL << info.alias() << " is already cached." << endl;
       RepoStatus cache_status = cacheStatus(info);
 
-      if ( cache_status.checksum() == raw_metadata_status.checksum() )
+      if ( cache_status == raw_metadata_status )
       {
         MIL << info.alias() << " cache is up to date with metadata." << endl;
         if ( policy == BuildIfNeeded ) {
@@ -1174,10 +1255,10 @@ namespace zypp
 
         ExternalProgram::Arguments cmd;
         cmd.push_back( "repo2solv.sh" );
-
         // repo2solv expects -o as 1st arg!
         cmd.push_back( "-o" );
         cmd.push_back( solvfile.asString() );
+       cmd.push_back( "-X" );  // autogenerate pattern from pattern-package
 
         if ( repokind == RepoType::RPMPLAINDIR )
         {
@@ -1215,7 +1296,7 @@ namespace zypp
       }
       break;
       default:
-        ZYPP_THROW(RepoUnknownTypeException( _("Unhandled repository type") ));
+        ZYPP_THROW(RepoUnknownTypeException( info, _("Unhandled repository type") ));
       break;
     }
     // update timestamp and checksum
@@ -1344,7 +1425,7 @@ namespace zypp
             if ( subdir->basename() == r->escaped_alias() )
             { found = true; break; }
 
-          if ( ! found )
+          if ( ! found && ( Date::now()-PathInfo(*subdir).mtime() > Date::day ) )
             filesystem::recursive_rmdir( *subdir );
 
           progress.set( progress.val() + sdircurrent * 100 / sdircount );
@@ -1426,7 +1507,7 @@ namespace zypp
     MIL << "Try adding repo " << info << endl;
 
     RepoInfo tosave = info;
-    if ( _repos.find(tosave) != _repos.end() )
+    if ( repos().find(tosave) != repos().end() )
       ZYPP_THROW(RepoAlreadyExistsException(info));
 
     // check the first url for now
@@ -1439,7 +1520,7 @@ namespace zypp
       if ( tosave.baseUrlsSize() > 0 )
       {
         if ( probedtype == RepoType::NONE )
-          ZYPP_THROW(RepoUnknownTypeException());
+          ZYPP_THROW(RepoUnknownTypeException(info));
         else
           tosave.setType(probedtype);
       }
@@ -1473,7 +1554,7 @@ namespace zypp
       oinfo.setMetadataPath( metadataPath( tosave ) );
       oinfo.setPackagesPath( packagesPath( tosave ) );
     }
-    _repos.insert(tosave);
+    reposManip().insert(tosave);
 
     progress.set(90);
 
@@ -1551,7 +1632,7 @@ namespace zypp
       MIL << "Saving " << (*it).alias() << endl;
       it->setFilepath(repofile.asString());
       it->dumpAsIniOn(file);
-      _repos.insert(*it);
+      reposManip().insert(*it);
 
       HistoryLog(_options.rootDir).addRepository(*it);
     }
@@ -1585,7 +1666,7 @@ namespace zypp
       RepoInfo todelete = *it;
       if (todelete.filepath().empty())
       {
-        ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
+        ZYPP_THROW(RepoException( todelete, _("Can't figure out where the repo is stored.") ));
       }
       else
       {
@@ -1597,9 +1678,9 @@ namespace zypp
           if ( filesystem::unlink(todelete.filepath()) != 0 )
           {
             // TranslatorExplanation '%s' is a filename
-            ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
+            ZYPP_THROW(RepoException( todelete, str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
           }
-          MIL << todelete.alias() << " sucessfully deleted." << endl;
+          MIL << todelete.alias() << " successfully deleted." << endl;
         }
         else
         {
@@ -1635,8 +1716,8 @@ namespace zypp
         // now delete metadata (#301037)
         cleanMetadata( todelete, mSubprogrcv );
        cleanPackages( todelete, pSubprogrcv );
-        _repos.erase(todelete);
-        MIL << todelete.alias() << " sucessfully deleted." << endl;
+        reposManip().erase(todelete);
+        MIL << todelete.alias() << " successfully deleted." << endl;
         HistoryLog(_options.rootDir).removeRepository(todelete);
         return;
       } // else filepath is empty
@@ -1661,7 +1742,7 @@ namespace zypp
 
     if (toedit.filepath().empty())
     {
-      ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
+      ZYPP_THROW(RepoException( toedit, _("Can't figure out where the repo is stored.") ));
     }
     else
     {
@@ -1695,8 +1776,8 @@ namespace zypp
       }
 
       newinfo.setFilepath(toedit.filepath());
-      _repos.erase(toedit);
-      _repos.insert(newinfo);
+      reposManip().erase(toedit);
+      reposManip().insert(newinfo);
       HistoryLog(_options.rootDir).modifyRepository(toedit, newinfo);
       MIL << "repo " << alias << " modified" << endl;
     }
@@ -1706,8 +1787,8 @@ namespace zypp
 
   RepoInfo RepoManager::Impl::getRepositoryInfo( const std::string & alias, const ProgressData::ReceiverFnc & progressrcv )
   {
-    RepoConstIterator it( findAlias( alias, _repos ) );
-    if ( it != _repos.end() )
+    RepoConstIterator it( findAlias( alias, repos() ) );
+    if ( it != repos().end() )
       return *it;
     RepoInfo info;
     info.setAlias( alias );
@@ -1767,14 +1848,14 @@ namespace zypp
 
   void RepoManager::Impl::removeService( const std::string & alias )
   {
-    MIL << "Going to delete repo " << alias << endl;
+    MIL << "Going to delete service " << alias << endl;
 
     const ServiceInfo & service = getService( alias );
 
     Pathname location = service.filepath();
     if( location.empty() )
     {
-      ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
+      ZYPP_THROW(ServiceException( service, _("Can't figure out where the service is stored.") ));
     }
 
     ServiceSet tmpSet;
@@ -1786,9 +1867,9 @@ namespace zypp
       if ( filesystem::unlink(location) != 0 )
       {
         // TranslatorExplanation '%s' is a filename
-        ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), location.c_str() )));
+        ZYPP_THROW(ServiceException( service, str::form( _("Can't delete '%s'"), location.c_str() ) ));
       }
-      MIL << alias << " sucessfully deleted." << endl;
+      MIL << alias << " successfully deleted." << endl;
     }
     else
     {
@@ -1807,7 +1888,7 @@ namespace zypp
           it->dumpAsIniOn(file);
       }
 
-      MIL << alias << " sucessfully deleted from file " << location <<  endl;
+      MIL << alias << " successfully deleted from file " << location <<  endl;
     }
 
     // now remove all repositories added by this service
@@ -1821,7 +1902,7 @@ namespace zypp
 
   ////////////////////////////////////////////////////////////////////////////
 
-  void RepoManager::Impl::refreshServices()
+  void RepoManager::Impl::refreshServices( const RefreshServiceOptions & options_r )
   {
     // copy the set of services since refreshService
     // can eventually invalidate the iterator
@@ -1832,14 +1913,14 @@ namespace zypp
         continue;
 
       try {
-       refreshService(*it);
+       refreshService(*it, options_r);
       }
       catch ( const repo::ServicePluginInformalException & e )
       { ;/* ignore ServicePluginInformalException */ }
     }
   }
 
-  void RepoManager::Impl::refreshService( const std::string & alias )
+  void RepoManager::Impl::refreshService( const std::string & alias, const RefreshServiceOptions & options_r )
   {
     ServiceInfo service( getService( alias ) );
     assert_alias( service );
@@ -1848,7 +1929,7 @@ namespace zypp
     // Either when probing the type, or when adjusting the repositories
     // enable/disable state.:
     bool serviceModified = false;
-    MIL << "Going to refresh service '" << service.alias() << "', url: "<< service.url() << endl;
+    MIL << "Going to refresh service '" << service.alias() << "', url: "<< service.url() << ", opts: " << options_r << endl;
 
     //! \todo add callbacks for apps (start, end, repo removed, repo added, repo changed)
 
@@ -1888,18 +1969,30 @@ namespace zypp
       uglyHack.second = e;
     }
 
+    ////////////////////////////////////////////////////////////////////////////
+    // On the fly remember the new repo states as defined the reopoindex.xml.
+    // Move into ServiceInfo later.
+    ServiceInfo::RepoStates newRepoStates;
+
     // set service alias and base url for all collected repositories
     for_( it, collector.repos.begin(), collector.repos.end() )
     {
+      // First of all: Prepend service alias:
+      it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
+      // set refrence to the parent service
+      it->setService( service.alias() );
+
+      // remember the new parsed repo state
+      newRepoStates[it->alias()] = *it;
+
       // if the repo url was not set by the repoindex parser, set service's url
       Url url;
-
       if ( it->baseUrlsEmpty() )
-        url = service.url();
+        url = service.rawUrl();
       else
       {
         // service repo can contain only one URL now, so no need to iterate.
-        url = *it->baseUrlsBegin();
+        url = it->rawUrl();    // raw!
       }
 
       // libzypp currently has problem with separate url + path handling
@@ -1912,13 +2005,8 @@ namespace zypp
         it->setPath("");
       }
 
-      // Prepend service alias:
-      it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
-
       // save the url
       it->setBaseUrl( url );
-      // set refrence to the parent service
-      it->setService( service.alias() );
     }
 
     ////////////////////////////////////////////////////////////////////////////
@@ -1927,22 +2015,29 @@ namespace zypp
     RepoInfoList oldRepos;
     getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
 
+    ////////////////////////////////////////////////////////////////////////////
     // find old repositories to remove...
-    for_( it, oldRepos.begin(), oldRepos.end() )
+    for_( oldRepo, oldRepos.begin(), oldRepos.end() )
     {
-      if ( ! foundAliasIn( it->alias(), collector.repos ) )
+      if ( ! foundAliasIn( oldRepo->alias(), collector.repos ) )
       {
-        if ( it->enabled() && ! service.repoToDisableFind( it->alias() ) )
-        {
-          DBG << "Service removes enabled repo " << it->alias() << endl;
-          service.addRepoToEnable( it->alias() );
-          serviceModified = true;
-        }
-        else
-        {
-          DBG << "Service removes disabled repo " << it->alias() << endl;
-        }
-        removeRepository( *it );
+       if ( oldRepo->enabled() )
+       {
+         // Currently enabled. If this was a user modification remember the state.
+         const auto & last = service.repoStates().find( oldRepo->alias() );
+         if ( last != service.repoStates().end() && ! last->second.enabled )
+         {
+           DBG << "Service removes user enabled repo " << oldRepo->alias() << endl;
+           service.addRepoToEnable( oldRepo->alias() );
+           serviceModified = true;
+         }
+         else
+           DBG << "Service removes enabled repo " << oldRepo->alias() << endl;
+       }
+       else
+         DBG << "Service removes disabled repo " << oldRepo->alias() << endl;
+
+        removeRepository( *oldRepo );
       }
     }
 
@@ -1950,24 +2045,40 @@ namespace zypp
     // create missing repositories and modify exising ones if needed...
     for_( it, collector.repos.begin(), collector.repos.end() )
     {
-      // Service explicitly requests the repo being enabled?
-      // Service explicitly requests the repo being disabled?
+      // User explicitly requested the repo being enabled?
+      // User explicitly requested the repo being disabled?
       // And hopefully not both ;) If so, enable wins.
-      bool beEnabled = service.repoToEnableFind( it->alias() );
-      bool beDisabled = service.repoToDisableFind( it->alias() );
 
-      // Make sure the service repo is created with the
-      // appropriate enable
-      if ( beEnabled ) it->setEnabled(true);
-      if ( beDisabled ) it->setEnabled(false);
+      TriBool toBeEnabled( indeterminate );    // indeterminate - follow the service request
+      DBG << "Service request to " << (it->enabled()?"enable":"disable") << " service repo " << it->alias() << endl;
 
-      if ( beEnabled )
+      if ( options_r.testFlag( RefreshService_restoreStatus ) )
       {
-        // Remove from enable request list.
-        // NOTE: repoToDisable is handled differently.
-        //       It gets cleared on each refresh.
-        service.delRepoToEnable( it->alias() );
-        serviceModified = true;
+       DBG << "Opt RefreshService_restoreStatus " << it->alias() << endl;
+       // this overrides any pending request!
+       // Remove from enable request list.
+       // NOTE: repoToDisable is handled differently.
+       //       It gets cleared on each refresh.
+       service.delRepoToEnable( it->alias() );
+       // toBeEnabled stays indeterminate!
+      }
+      else
+      {
+       if ( service.repoToEnableFind( it->alias() ) )
+       {
+         DBG << "User request to enable service repo " << it->alias() << endl;
+         toBeEnabled = true;
+         // Remove from enable request list.
+         // NOTE: repoToDisable is handled differently.
+         //       It gets cleared on each refresh.
+         service.delRepoToEnable( it->alias() );
+         serviceModified = true;
+       }
+       else if ( service.repoToDisableFind( it->alias() ) )
+       {
+         DBG << "User request to disable service repo " << it->alias() << endl;
+         toBeEnabled = false;
+       }
       }
 
       RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
@@ -1975,58 +2086,92 @@ namespace zypp
       {
         // Not found in oldRepos ==> a new repo to add
 
-        // At that point check whether a repo with the same alias
-        // exists outside this service. Maybe forcefully re-alias
-        // the existing repo?
+       // Make sure the service repo is created with the appropriate enablement
+       if ( ! indeterminate(toBeEnabled) )
+         it->setEnabled( toBeEnabled );
+
         DBG << "Service adds repo " << it->alias() << " " << (it->enabled()?"enabled":"disabled") << endl;
         addRepository( *it );
-
-        // save repo credentials
-        // ma@: task for modifyRepository?
       }
       else
       {
         // ==> an exising repo to check
         bool oldRepoModified = false;
 
+       if ( indeterminate(toBeEnabled) )
+       {
+         // No user request: check for an old user modificaton otherwise follow service request.
+         // NOTE: Assert toBeEnabled is boolean afterwards!
+         if ( oldRepo->enabled() == it->enabled() )
+           toBeEnabled = it->enabled();        // service requests no change to the system
+         else if (options_r.testFlag( RefreshService_restoreStatus ) )
+         {
+           toBeEnabled = it->enabled();        // RefreshService_restoreStatus forced
+           DBG << "Opt RefreshService_restoreStatus " << it->alias() <<  " forces " << (toBeEnabled?"enabled":"disabled") << endl;
+         }
+         else
+         {
+           const auto & last = service.repoStates().find( oldRepo->alias() );
+           if ( last == service.repoStates().end() || last->second.enabled != it->enabled() )
+             toBeEnabled = it->enabled();      // service request has changed since last refresh -> follow
+           else
+           {
+             toBeEnabled = oldRepo->enabled(); // service request unchaned since last refresh -> keep user modification
+             DBG << "User modified service repo " << it->alias() <<  " may stay " << (toBeEnabled?"enabled":"disabled") << endl;
+           }
+         }
+       }
+
         // changed enable?
-        if ( beEnabled )
-        {
-          if ( ! oldRepo->enabled() )
-          {
-            DBG << "Service repo " << it->alias() << " gets enabled" << endl;
-            oldRepo->setEnabled( true );
-            oldRepoModified = true;
-          }
-          else
-          {
-            DBG << "Service repo " << it->alias() << " stays enabled" << endl;
-          }
-        }
-        else if ( beDisabled )
-        {
-          if ( oldRepo->enabled() )
-          {
-            DBG << "Service repo " << it->alias() << " gets disabled" << endl;
-            oldRepo->setEnabled( false );
-            oldRepoModified = true;
-          }
-          else
-          {
-            DBG << "Service repo " << it->alias() << " stays disabled" << endl;
-          }
-        }
-        else
+       if ( toBeEnabled == oldRepo->enabled() )
+       {
+         DBG << "Service repo " << it->alias() << " stays " <<  (oldRepo->enabled()?"enabled":"disabled") << endl;
+       }
+       else if ( toBeEnabled )
+       {
+         DBG << "Service repo " << it->alias() << " gets enabled" << endl;
+         oldRepo->setEnabled( true );
+         oldRepoModified = true;
+       }
+       else
         {
-          DBG << "Service repo " << it->alias() << " stays " <<  (oldRepo->enabled()?"enabled":"disabled") << endl;
-        }
+         DBG << "Service repo " << it->alias() << " gets disabled" << endl;
+         oldRepo->setEnabled( false );
+         oldRepoModified = true;
+       }
+
+       // all other attributes follow the service request:
+
+       // changed name (raw!)
+       if ( oldRepo->rawName() != it->rawName() )
+       {
+         DBG << "Service repo " << it->alias() << " gets new NAME " << it->rawName() << endl;
+         oldRepo->setName( it->rawName() );
+         oldRepoModified = true;
+       }
+
+       // changed autorefresh
+       if ( oldRepo->autorefresh() != it->autorefresh() )
+       {
+         DBG << "Service repo " << it->alias() << " gets new AUTOREFRESH " << it->autorefresh() << endl;
+         oldRepo->setAutorefresh( it->autorefresh() );
+         oldRepoModified = true;
+       }
+
+       // changed priority?
+       if ( oldRepo->priority() != it->priority() )
+       {
+         DBG << "Service repo " << it->alias() << " gets new PRIORITY " << it->priority() << endl;
+         oldRepo->setPriority( it->priority() );
+         oldRepoModified = true;
+       }
 
         // changed url?
         // service repo can contain only one URL now, so no need to iterate.
-        if ( oldRepo->url() != it->url() )
+        if ( oldRepo->rawUrl() != it->rawUrl() )
         {
-          DBG << "Service repo " << it->alias() << " gets new URL " << it->url() << endl;
-          oldRepo->setBaseUrl( it->url() );
+          DBG << "Service repo " << it->alias() << " gets new URL " << it->rawUrl() << endl;
+          oldRepo->setBaseUrl( it->rawUrl() );
           oldRepoModified = true;
         }
 
@@ -2045,9 +2190,16 @@ namespace zypp
       serviceModified = true;
     }
 
+    // Remember original service request for next refresh
+    if ( service.repoStates() != newRepoStates )
+    {
+      service.setRepoStates( std::move(newRepoStates) );
+      serviceModified = true;
+    }
+
     ////////////////////////////////////////////////////////////////////////////
-    // save service if modified:
-    if ( serviceModified )
+    // save service if modified: (unless a plugin service)
+    if ( serviceModified && service.type() != ServiceType::PLUGIN )
     {
       // write out modified service file.
       modifyService( service.alias(), service );
@@ -2071,8 +2223,7 @@ namespace zypp
 
     if ( service.type() == ServiceType::PLUGIN )
     {
-        MIL << "Not modifying plugin service '" << oldAlias << "'" << endl;
-        return;
+      ZYPP_THROW(ServicePluginImmutableException( service ));
     }
 
     const ServiceInfo & oldService = getService(oldAlias);
@@ -2080,7 +2231,7 @@ namespace zypp
     Pathname location = oldService.filepath();
     if( location.empty() )
     {
-      ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
+      ZYPP_THROW(ServiceException( oldService, _("Can't figure out where the service is stored.") ));
     }
 
     // remember: there may multiple services being defined in one file:
@@ -2102,23 +2253,29 @@ namespace zypp
     _services.insert(service);
 
     // changed properties affecting also repositories
-    if( oldAlias != service.alias()                    // changed alias
-        || oldService.enabled() != service.enabled()   // changed enabled status
-      )
+    if ( oldAlias != service.alias()                   // changed alias
+      || oldService.enabled() != service.enabled() )   // changed enabled status
     {
       std::vector<RepoInfo> toModify;
       getRepositoriesInService(oldAlias, std::back_inserter(toModify));
       for_( it, toModify.begin(), toModify.end() )
       {
-        if (oldService.enabled() && !service.enabled())
-          it->setEnabled(false);
-        else if (!oldService.enabled() && service.enabled())
-        {
-          //! \todo do nothing? the repos will be enabled on service refresh
-          //! \todo how to know the service needs a (auto) refresh????
-        }
-        else
+       if ( oldService.enabled() != service.enabled() )
+       {
+         if ( service.enabled() )
+         {
+           // reset to last refreshs state
+           const auto & last = service.repoStates().find( it->alias() );
+           if ( last != service.repoStates().end() )
+             it->setEnabled( last->second.enabled );
+         }
+         else
+           it->setEnabled( false );
+       }
+
+        if ( oldAlias != service.alias() )
           it->setService(service.alias());
+
         modifyRepository(it->alias(), *it);
       }
     }
@@ -2303,14 +2460,14 @@ namespace zypp
   void RepoManager::removeService( const ServiceInfo & service )
   { return _pimpl->removeService( service ); }
 
-  void RepoManager::refreshServices()
-  { return _pimpl->refreshServices(); }
+  void RepoManager::refreshServices( const RefreshServiceOptions & options_r )
+  { return _pimpl->refreshServices( options_r ); }
 
-  void RepoManager::refreshService( const std::string & alias )
-  { return _pimpl->refreshService( alias ); }
+  void RepoManager::refreshService( const std::string & alias, const RefreshServiceOptions & options_r )
+  { return _pimpl->refreshService( alias, options_r ); }
 
-  void RepoManager::refreshService( const ServiceInfo & service )
-  { return _pimpl->refreshService( service ); }
+  void RepoManager::refreshService( const ServiceInfo & service, const RefreshServiceOptions & options_r )
+  { return _pimpl->refreshService( service, options_r ); }
 
   void RepoManager::modifyService( const std::string & oldAlias, const ServiceInfo & service )
   { return _pimpl->modifyService( oldAlias, service ); }