Imported Upstream version 15.0.0
[platform/upstream/libzypp.git] / zypp / RepoManager.cc
index 894902d..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"
@@ -148,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
@@ -203,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);
     }
 
     ////////////////////////////////////////////////////////////////////////////
@@ -220,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;
@@ -247,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] == '.')
@@ -258,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] == '.')
@@ -399,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.
@@ -414,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:
@@ -497,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 );
 
@@ -531,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);
     }
 
@@ -540,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 */
@@ -630,6 +706,37 @@ 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;
@@ -637,37 +744,60 @@ namespace zypp
     if ( PathInfo(_options.knownReposPath).isExist() )
     {
       std::list<std::string> repoEscAliases;
+      std::list<RepoInfo> orphanedRepos;
       for ( RepoInfo & repoInfo : repositories_in_dir(_options.knownReposPath) )
       {
         // set the metadata path for the repo
         repoInfo.setMetadataPath( rawcache_path_for_repoinfo(_options, repoInfo) );
        // set the downloaded packages path for the repo
        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( repoInfo );
         repoEscAliases.push_back(repoInfo.escaped_alias());
       }
-      repoEscAliases.sort();
 
-      // delete metadata folders without corresponding repo (e.g. old tmp directories)
-      for ( const Pathname & cachePath : { _options.repoRawCachePath
-                                        , _options.repoSolvCachePath } )
+      // Cleanup orphanded service repos:
+      if ( ! orphanedRepos.empty() )
       {
-       std::list<std::string> entries;
-       if ( filesystem::readdir( entries, cachePath, false ) == 0 )
+       for ( auto & repoInfo : orphanedRepos )
        {
-         entries.sort();
-         std::set<std::string> oldfiles;
-         set_difference( entries.begin(), entries.end(), repoEscAliases.begin(), repoEscAliases.end(),
-                         std::inserter( oldfiles, oldfiles.end() ) );
-         for ( const std::string & old : oldfiles )
+         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 )
          {
-           if ( old == Repository::systemRepoAlias() ) // don't remove the @System solv file
-             continue;
-           filesystem::recursive_rmdir( cachePath / old );
+           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;
   }
@@ -805,9 +935,6 @@ 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 unknown: probe it
       if ( repokind == RepoType::NONE )
@@ -881,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 )
@@ -979,12 +1109,13 @@ namespace zypp
         }
         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;
@@ -1165,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
@@ -1376,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
@@ -1389,7 +1520,7 @@ namespace zypp
       if ( tosave.baseUrlsSize() > 0 )
       {
         if ( probedtype == RepoType::NONE )
-          ZYPP_THROW(RepoUnknownTypeException());
+          ZYPP_THROW(RepoUnknownTypeException(info));
         else
           tosave.setType(probedtype);
       }
@@ -1423,7 +1554,7 @@ namespace zypp
       oinfo.setMetadataPath( metadataPath( tosave ) );
       oinfo.setPackagesPath( packagesPath( tosave ) );
     }
-    _repos.insert(tosave);
+    reposManip().insert(tosave);
 
     progress.set(90);
 
@@ -1501,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);
     }
@@ -1535,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
       {
@@ -1547,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
         {
@@ -1585,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
@@ -1611,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
     {
@@ -1645,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;
     }
@@ -1656,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 );
@@ -1717,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;
@@ -1736,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
     {
@@ -1757,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
@@ -1771,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
@@ -1782,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 );
@@ -1798,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)
 
@@ -1857,11 +1988,11 @@ namespace zypp
       // 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
@@ -1921,23 +2052,35 @@ namespace zypp
       TriBool toBeEnabled( indeterminate );    // indeterminate - follow the service request
       DBG << "Service request to " << (it->enabled()?"enable":"disable") << " service repo " << it->alias() << endl;
 
-      if ( service.repoToEnableFind( it->alias() ) )
+      if ( options_r.testFlag( RefreshService_restoreStatus ) )
       {
-       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;
+       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.repoToDisableFind( it->alias() ) )
+      else
       {
-       DBG << "User request to disable service repo " << it->alias() << endl;
-       toBeEnabled = false;
+       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 ) );
       if ( oldRepo == oldRepos.end() )
       {
@@ -1961,6 +2104,11 @@ namespace zypp
          // 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() );
@@ -1994,6 +2142,14 @@ namespace zypp
 
        // 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() )
        {
@@ -2012,10 +2168,10 @@ namespace zypp
 
         // 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;
         }
 
@@ -2042,8 +2198,8 @@ namespace zypp
     }
 
     ////////////////////////////////////////////////////////////////////////////
-    // 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 );
@@ -2067,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);
@@ -2076,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:
@@ -2098,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);
       }
     }
@@ -2299,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 ); }