libzypp 8.13.6
|
00001 /*---------------------------------------------------------------------\ 00002 | ____ _ __ __ ___ | 00003 | |__ / \ / / . \ . \ | 00004 | / / \ V /| _/ _/ | 00005 | / /__ | | | | | | | 00006 | /_____||_| |_| |_| | 00007 | | 00008 \---------------------------------------------------------------------*/ 00013 #include <cstdlib> 00014 #include <iostream> 00015 #include <fstream> 00016 #include <sstream> 00017 #include <list> 00018 #include <map> 00019 #include <algorithm> 00020 00021 #include "zypp/base/InputStream.h" 00022 #include "zypp/base/LogTools.h" 00023 #include "zypp/base/Gettext.h" 00024 #include "zypp/base/Function.h" 00025 #include "zypp/base/Regex.h" 00026 #include "zypp/PathInfo.h" 00027 #include "zypp/TmpPath.h" 00028 00029 #include "zypp/ServiceInfo.h" 00030 #include "zypp/repo/RepoException.h" 00031 #include "zypp/RepoManager.h" 00032 00033 #include "zypp/media/MediaManager.h" 00034 #include "zypp/media/CredentialManager.h" 00035 #include "zypp/MediaSetAccess.h" 00036 #include "zypp/ExternalProgram.h" 00037 #include "zypp/ManagedFile.h" 00038 00039 #include "zypp/parser/RepoFileReader.h" 00040 #include "zypp/parser/ServiceFileReader.h" 00041 #include "zypp/repo/ServiceRepos.h" 00042 #include "zypp/repo/yum/Downloader.h" 00043 #include "zypp/repo/susetags/Downloader.h" 00044 #include "zypp/parser/plaindir/RepoParser.h" 00045 #include "zypp/repo/PluginServices.h" 00046 00047 #include "zypp/Target.h" // for Target::targetDistribution() for repo index services 00048 #include "zypp/ZYppFactory.h" // to get the Target from ZYpp instance 00049 #include "zypp/HistoryLog.h" // to write history :O) 00050 00051 #include "zypp/ZYppCallbacks.h" 00052 00053 #include "sat/Pool.h" 00054 00055 using std::endl; 00056 using std::string; 00057 using namespace zypp::repo; 00058 00060 namespace zypp 00061 { 00062 00063 namespace 00064 { 00068 class MediaMounter 00069 { 00070 public: 00072 MediaMounter( const Url & url_r ) 00073 { 00074 media::MediaManager mediamanager; 00075 _mid = mediamanager.open( url_r ); 00076 mediamanager.attach( _mid ); 00077 } 00078 00080 ~MediaMounter() 00081 { 00082 media::MediaManager mediamanager; 00083 mediamanager.release( _mid ); 00084 mediamanager.close( _mid ); 00085 } 00086 00091 Pathname getPathName( const Pathname & path_r = Pathname() ) const 00092 { 00093 media::MediaManager mediamanager; 00094 return mediamanager.localPath( _mid, path_r ); 00095 } 00096 00097 private: 00098 media::MediaAccessId _mid; 00099 }; 00100 00102 template <class Iterator> 00103 inline bool foundAliasIn( const std::string & alias_r, Iterator begin_r, Iterator end_r ) 00104 { 00105 for_( it, begin_r, end_r ) 00106 if ( it->alias() == alias_r ) 00107 return true; 00108 return false; 00109 } 00111 template <class Container> 00112 inline bool foundAliasIn( const std::string & alias_r, const Container & cont_r ) 00113 { return foundAliasIn( alias_r, cont_r.begin(), cont_r.end() ); } 00114 00116 template <class Iterator> 00117 inline Iterator findAlias( const std::string & alias_r, Iterator begin_r, Iterator end_r ) 00118 { 00119 for_( it, begin_r, end_r ) 00120 if ( it->alias() == alias_r ) 00121 return it; 00122 return end_r; 00123 } 00125 template <class Container> 00126 inline typename Container::iterator findAlias( const std::string & alias_r, Container & cont_r ) 00127 { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); } 00129 template <class Container> 00130 inline typename Container::const_iterator findAlias( const std::string & alias_r, const Container & cont_r ) 00131 { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); } 00132 } 00133 00135 // 00136 // CLASS NAME : RepoManagerOptions 00137 // 00139 00140 RepoManagerOptions::RepoManagerOptions( const Pathname & root_r ) 00141 { 00142 repoCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoCachePath() ); 00143 repoRawCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoMetadataPath() ); 00144 repoSolvCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoSolvfilesPath() ); 00145 repoPackagesCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoPackagesPath() ); 00146 knownReposPath = Pathname::assertprefix( root_r, ZConfig::instance().knownReposPath() ); 00147 knownServicesPath = Pathname::assertprefix( root_r, ZConfig::instance().knownServicesPath() ); 00148 pluginsPath = Pathname::assertprefix( root_r, ZConfig::instance().pluginsPath() ); 00149 probe = ZConfig::instance().repo_add_probe(); 00150 00151 rootDir = root_r; 00152 } 00153 00154 RepoManagerOptions RepoManagerOptions::makeTestSetup( const Pathname & root_r ) 00155 { 00156 RepoManagerOptions ret; 00157 ret.repoCachePath = root_r; 00158 ret.repoRawCachePath = root_r/"raw"; 00159 ret.repoSolvCachePath = root_r/"solv"; 00160 ret.repoPackagesCachePath = root_r/"packages"; 00161 ret.knownReposPath = root_r/"repos.d"; 00162 ret.knownServicesPath = root_r/"services.d"; 00163 ret.pluginsPath = root_r/"plugins"; 00164 ret.rootDir = root_r; 00165 return ret; 00166 } 00167 00169 00185 struct RepoCollector : private base::NonCopyable 00186 { 00187 RepoCollector() 00188 {} 00189 00190 RepoCollector(const std::string & targetDistro_) 00191 : targetDistro(targetDistro_) 00192 {} 00193 00194 bool collect( const RepoInfo &repo ) 00195 { 00196 // skip repositories meant for other distros than specified 00197 if (!targetDistro.empty() 00198 && !repo.targetDistribution().empty() 00199 && repo.targetDistribution() != targetDistro) 00200 { 00201 MIL 00202 << "Skipping repository meant for '" << targetDistro 00203 << "' distribution (current distro is '" 00204 << repo.targetDistribution() << "')." << endl; 00205 00206 return true; 00207 } 00208 00209 repos.push_back(repo); 00210 return true; 00211 } 00212 00213 RepoInfoList repos; 00214 std::string targetDistro; 00215 }; 00216 00218 00224 static std::list<RepoInfo> repositories_in_file( const Pathname & file ) 00225 { 00226 MIL << "repo file: " << file << endl; 00227 RepoCollector collector; 00228 parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) ); 00229 return collector.repos; 00230 } 00231 00233 00242 static std::list<RepoInfo> repositories_in_dir( const Pathname &dir ) 00243 { 00244 MIL << "directory " << dir << endl; 00245 std::list<RepoInfo> repos; 00246 std::list<Pathname> entries; 00247 if ( filesystem::readdir( entries, dir, false ) != 0 ) 00248 { 00249 // TranslatorExplanation '%s' is a pathname 00250 ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str()))); 00251 } 00252 00253 str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$"); 00254 for ( std::list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it ) 00255 { 00256 if (str::regex_match(it->extension(), allowedRepoExt)) 00257 { 00258 std::list<RepoInfo> tmp = repositories_in_file( *it ); 00259 repos.insert( repos.end(), tmp.begin(), tmp.end() ); 00260 00261 //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos)); 00262 //MIL << "ok" << endl; 00263 } 00264 } 00265 return repos; 00266 } 00267 00269 00270 std::list<RepoInfo> readRepoFile(const Url & repo_file) 00271 { 00272 // no interface to download a specific file, using workaround: 00274 Url url(repo_file); 00275 Pathname path(url.getPathName()); 00276 url.setPathName ("/"); 00277 MediaSetAccess access(url); 00278 Pathname local = access.provideFile(path); 00279 00280 DBG << "reading repo file " << repo_file << ", local path: " << local << endl; 00281 00282 return repositories_in_file(local); 00283 } 00284 00286 00287 inline void assert_alias( const RepoInfo & info ) 00288 { 00289 if ( info.alias().empty() ) 00290 ZYPP_THROW( RepoNoAliasException() ); 00291 // bnc #473834. Maybe we can match the alias against a regex to define 00292 // and check for valid aliases 00293 if ( info.alias()[0] == '.') 00294 ZYPP_THROW(RepoInvalidAliasException( 00295 info, _("Repository alias cannot start with dot."))); 00296 } 00297 00298 inline void assert_alias( const ServiceInfo & info ) 00299 { 00300 if ( info.alias().empty() ) 00301 ZYPP_THROW( ServiceNoAliasException() ); 00302 // bnc #473834. Maybe we can match the alias against a regex to define 00303 // and check for valid aliases 00304 if ( info.alias()[0] == '.') 00305 ZYPP_THROW(ServiceInvalidAliasException( 00306 info, _("Service alias cannot start with dot."))); 00307 } 00308 00310 00311 inline void assert_urls( const RepoInfo & info ) 00312 { 00313 if ( info.baseUrlsEmpty() ) 00314 ZYPP_THROW( RepoNoUrlException( info ) ); 00315 } 00316 00317 inline void assert_url( const ServiceInfo & info ) 00318 { 00319 if ( ! info.url().isValid() ) 00320 ZYPP_THROW( ServiceNoUrlException( info ) ); 00321 } 00322 00324 00329 inline Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info ) 00330 { 00331 assert_alias(info); 00332 return opt.repoRawCachePath / info.escaped_alias(); 00333 } 00334 00343 inline Pathname rawproductdata_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info ) 00344 { 00345 assert_alias(info); 00346 return opt.repoRawCachePath / info.escaped_alias() / info.path(); 00347 } 00348 00349 00353 inline Pathname packagescache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info ) 00354 { 00355 assert_alias(info); 00356 return opt.repoPackagesCachePath / info.escaped_alias(); 00357 } 00358 00362 inline Pathname solv_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info) 00363 { 00364 assert_alias(info); 00365 return opt.repoSolvCachePath / info.escaped_alias(); 00366 } 00367 00369 00371 class ServiceCollector 00372 { 00373 public: 00374 typedef std::set<ServiceInfo> ServiceSet; 00375 00376 ServiceCollector( ServiceSet & services_r ) 00377 : _services( services_r ) 00378 {} 00379 00380 bool operator()( const ServiceInfo & service_r ) const 00381 { 00382 _services.insert( service_r ); 00383 return true; 00384 } 00385 00386 private: 00387 ServiceSet & _services; 00388 }; 00389 00391 00393 // 00394 // CLASS NAME : RepoManager::Impl 00395 // 00397 00401 struct RepoManager::Impl 00402 { 00403 Impl( const RepoManagerOptions &opt ) 00404 : options(opt) 00405 { 00406 init_knownServices(); 00407 init_knownRepositories(); 00408 } 00409 00410 00411 RepoManagerOptions options; 00412 00413 RepoSet repos; 00414 00415 ServiceSet services; 00416 00417 public: 00418 00419 void saveService( ServiceInfo & service ) const; 00420 00421 Pathname generateNonExistingName( const Pathname &dir, 00422 const std::string &basefilename ) const; 00423 00424 std::string generateFilename( const RepoInfo & info ) const; 00425 std::string generateFilename( const ServiceInfo & info ) const; 00426 00427 00428 private: 00429 void init_knownServices(); 00430 void init_knownRepositories(); 00431 00432 private: 00433 friend Impl * rwcowClone<Impl>( const Impl * rhs ); 00435 Impl * clone() const 00436 { return new Impl( *this ); } 00437 }; 00438 00440 00442 inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj ) 00443 { 00444 return str << "RepoManager::Impl"; 00445 } 00446 00448 00449 void RepoManager::Impl::saveService( ServiceInfo & service ) const 00450 { 00451 filesystem::assert_dir( options.knownServicesPath ); 00452 Pathname servfile = generateNonExistingName( options.knownServicesPath, 00453 generateFilename( service ) ); 00454 service.setFilepath( servfile ); 00455 00456 MIL << "saving service in " << servfile << endl; 00457 00458 std::ofstream file( servfile.c_str() ); 00459 if ( !file ) 00460 { 00461 // TranslatorExplanation '%s' is a filename 00462 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), servfile.c_str() ))); 00463 } 00464 service.dumpAsIniOn( file ); 00465 MIL << "done" << endl; 00466 } 00467 00483 Pathname RepoManager::Impl::generateNonExistingName( const Pathname & dir, 00484 const std::string & basefilename ) const 00485 { 00486 std::string final_filename = basefilename; 00487 int counter = 1; 00488 while ( PathInfo(dir + final_filename).isExist() ) 00489 { 00490 final_filename = basefilename + "_" + str::numstring(counter); 00491 counter++; 00492 } 00493 return dir + Pathname(final_filename); 00494 } 00495 00497 00505 std::string RepoManager::Impl::generateFilename( const RepoInfo & info ) const 00506 { 00507 std::string filename = info.alias(); 00508 // replace slashes with underscores 00509 str::replaceAll( filename, "/", "_" ); 00510 00511 filename = Pathname(filename).extend(".repo").asString(); 00512 MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl; 00513 return filename; 00514 } 00515 00516 std::string RepoManager::Impl::generateFilename( const ServiceInfo & info ) const 00517 { 00518 std::string filename = info.alias(); 00519 // replace slashes with underscores 00520 str::replaceAll( filename, "/", "_" ); 00521 00522 filename = Pathname(filename).extend(".service").asString(); 00523 MIL << "generating filename for service [" << info.alias() << "] : '" << filename << "'" << endl; 00524 return filename; 00525 } 00526 00527 00528 void RepoManager::Impl::init_knownServices() 00529 { 00530 Pathname dir = options.knownServicesPath; 00531 std::list<Pathname> entries; 00532 if (PathInfo(dir).isExist()) 00533 { 00534 if ( filesystem::readdir( entries, dir, false ) != 0 ) 00535 { 00536 // TranslatorExplanation '%s' is a pathname 00537 ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str()))); 00538 } 00539 00540 //str::regex allowedServiceExt("^\\.service(_[0-9]+)?$"); 00541 for_(it, entries.begin(), entries.end() ) 00542 { 00543 parser::ServiceFileReader(*it, ServiceCollector(services)); 00544 } 00545 } 00546 00547 repo::PluginServices(options.pluginsPath/"services", ServiceCollector(services)); 00548 } 00549 00550 void RepoManager::Impl::init_knownRepositories() 00551 { 00552 MIL << "start construct known repos" << endl; 00553 00554 if ( PathInfo(options.knownReposPath).isExist() ) 00555 { 00556 RepoInfoList repol = repositories_in_dir(options.knownReposPath); 00557 for ( RepoInfoList::iterator it = repol.begin(); 00558 it != repol.end(); 00559 ++it ) 00560 { 00561 // set the metadata path for the repo 00562 Pathname metadata_path = rawcache_path_for_repoinfo(options, (*it)); 00563 (*it).setMetadataPath(metadata_path); 00564 00565 // set the downloaded packages path for the repo 00566 Pathname packages_path = packagescache_path_for_repoinfo(options, (*it)); 00567 (*it).setPackagesPath(packages_path); 00568 00569 repos.insert(*it); 00570 } 00571 } 00572 00573 MIL << "end construct known repos" << endl; 00574 } 00575 00577 // 00578 // CLASS NAME : RepoManager 00579 // 00581 00582 RepoManager::RepoManager( const RepoManagerOptions &opt ) 00583 : _pimpl( new Impl(opt) ) 00584 {} 00585 00587 00588 RepoManager::~RepoManager() 00589 {} 00590 00592 00593 bool RepoManager::repoEmpty() const 00594 { return _pimpl->repos.empty(); } 00595 00596 RepoManager::RepoSizeType RepoManager::repoSize() const 00597 { return _pimpl->repos.size(); } 00598 00599 RepoManager::RepoConstIterator RepoManager::repoBegin() const 00600 { return _pimpl->repos.begin(); } 00601 00602 RepoManager::RepoConstIterator RepoManager::repoEnd() const 00603 { return _pimpl->repos.end(); } 00604 00605 RepoInfo RepoManager::getRepo( const std::string & alias ) const 00606 { 00607 for_( it, repoBegin(), repoEnd() ) 00608 if ( it->alias() == alias ) 00609 return *it; 00610 return RepoInfo::noRepo; 00611 } 00612 00613 bool RepoManager::hasRepo( const std::string & alias ) const 00614 { 00615 for_( it, repoBegin(), repoEnd() ) 00616 if ( it->alias() == alias ) 00617 return true; 00618 return false; 00619 } 00620 00621 std::string RepoManager::makeStupidAlias( const Url & url_r ) 00622 { 00623 std::string ret( url_r.getScheme() ); 00624 if ( ret.empty() ) 00625 ret = "repo-"; 00626 else 00627 ret += "-"; 00628 00629 std::string host( url_r.getHost() ); 00630 if ( ! host.empty() ) 00631 { 00632 ret += host; 00633 ret += "-"; 00634 } 00635 00636 static Date::ValueType serial = Date::now(); 00637 ret += Digest::digest( Digest::sha1(), str::hexstring( ++serial ) +url_r.asCompleteString() ).substr(0,8); 00638 return ret; 00639 } 00640 00642 00643 Pathname RepoManager::metadataPath( const RepoInfo &info ) const 00644 { 00645 return rawcache_path_for_repoinfo(_pimpl->options, info ); 00646 } 00647 00648 Pathname RepoManager::packagesPath( const RepoInfo &info ) const 00649 { 00650 return packagescache_path_for_repoinfo(_pimpl->options, info ); 00651 } 00652 00654 00655 RepoStatus RepoManager::metadataStatus( const RepoInfo &info ) const 00656 { 00657 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info ); 00658 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info ); 00659 RepoType repokind = info.type(); 00660 RepoStatus status; 00661 00662 switch ( repokind.toEnum() ) 00663 { 00664 case RepoType::NONE_e: 00665 // unknown, probe the local metadata 00666 repokind = probe( productdatapath.asUrl() ); 00667 break; 00668 default: 00669 break; 00670 } 00671 00672 switch ( repokind.toEnum() ) 00673 { 00674 case RepoType::RPMMD_e : 00675 { 00676 status = RepoStatus( productdatapath + "/repodata/repomd.xml"); 00677 } 00678 break; 00679 00680 case RepoType::YAST2_e : 00681 { 00682 status = RepoStatus( productdatapath + "/content") && (RepoStatus( mediarootpath + "/media.1/media")); 00683 } 00684 break; 00685 00686 case RepoType::RPMPLAINDIR_e : 00687 { 00688 if ( PathInfo(Pathname(productdatapath + "/cookie")).isExist() ) 00689 status = RepoStatus( productdatapath + "/cookie"); 00690 } 00691 break; 00692 00693 case RepoType::NONE_e : 00694 // Return default RepoStatus in case of RepoType::NONE 00695 // indicating it should be created? 00696 // ZYPP_THROW(RepoUnknownTypeException()); 00697 break; 00698 } 00699 return status; 00700 } 00701 00702 void RepoManager::touchIndexFile(const RepoInfo & info) 00703 { 00704 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info ); 00705 00706 RepoType repokind = info.type(); 00707 if ( repokind.toEnum() == RepoType::NONE_e ) 00708 // unknown, probe the local metadata 00709 repokind = probe( productdatapath.asUrl() ); 00710 // if still unknown, just return 00711 if (repokind == RepoType::NONE_e) 00712 return; 00713 00714 Pathname p; 00715 switch ( repokind.toEnum() ) 00716 { 00717 case RepoType::RPMMD_e : 00718 p = Pathname(productdatapath + "/repodata/repomd.xml"); 00719 break; 00720 00721 case RepoType::YAST2_e : 00722 p = Pathname(productdatapath + "/content"); 00723 break; 00724 00725 case RepoType::RPMPLAINDIR_e : 00726 p = Pathname(productdatapath + "/cookie"); 00727 break; 00728 00729 case RepoType::NONE_e : 00730 default: 00731 break; 00732 } 00733 00734 // touch the file, ignore error (they are logged anyway) 00735 filesystem::touch(p); 00736 } 00737 00738 RepoManager::RefreshCheckStatus RepoManager::checkIfToRefreshMetadata( 00739 const RepoInfo &info, 00740 const Url &url, 00741 RawMetadataRefreshPolicy policy ) 00742 { 00743 assert_alias(info); 00744 00745 RepoStatus oldstatus; 00746 RepoStatus newstatus; 00747 00748 try 00749 { 00750 MIL << "Going to try to check whether refresh is needed for " << url << endl; 00751 00752 // first check old (cached) metadata 00753 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info ); 00754 filesystem::assert_dir(mediarootpath); 00755 oldstatus = metadataStatus(info); 00756 00757 if ( oldstatus.empty() ) 00758 { 00759 MIL << "No cached metadata, going to refresh" << endl; 00760 return REFRESH_NEEDED; 00761 } 00762 00763 { 00764 std::string scheme( url.getScheme() ); 00765 if ( scheme == "cd" || scheme == "dvd" ) 00766 { 00767 MIL << "never refresh CD/DVD" << endl; 00768 return REPO_UP_TO_DATE; 00769 } 00770 } 00771 00772 // now we've got the old (cached) status, we can decide repo.refresh.delay 00773 if (policy != RefreshForced && policy != RefreshIfNeededIgnoreDelay) 00774 { 00775 // difference in seconds 00776 double diff = difftime( 00777 (Date::ValueType)Date::now(), 00778 (Date::ValueType)oldstatus.timestamp()) / 60; 00779 00780 DBG << "oldstatus: " << (Date::ValueType)oldstatus.timestamp() << endl; 00781 DBG << "current time: " << (Date::ValueType)Date::now() << endl; 00782 DBG << "last refresh = " << diff << " minutes ago" << endl; 00783 00784 if ( diff < ZConfig::instance().repo_refresh_delay() ) 00785 { 00786 if ( diff < 0 ) 00787 { 00788 WAR << "Repository '" << info.alias() << "' was refreshed in the future!" << endl; 00789 } 00790 else 00791 { 00792 MIL << "Repository '" << info.alias() 00793 << "' has been refreshed less than repo.refresh.delay (" 00794 << ZConfig::instance().repo_refresh_delay() 00795 << ") minutes ago. Advising to skip refresh" << endl; 00796 return REPO_CHECK_DELAYED; 00797 } 00798 } 00799 } 00800 00801 // To test the new matadta create temp dir as sibling of mediarootpath 00802 filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) ); 00803 00804 repo::RepoType repokind = info.type(); 00805 // if the type is unknown, try probing. 00806 switch ( repokind.toEnum() ) 00807 { 00808 case RepoType::NONE_e: 00809 // unknown, probe it \todo respect productdir 00810 repokind = probe( url, info.path() ); 00811 break; 00812 default: 00813 break; 00814 } 00815 00816 if ( ( repokind.toEnum() == RepoType::RPMMD_e ) || 00817 ( repokind.toEnum() == RepoType::YAST2_e ) ) 00818 { 00819 MediaSetAccess media(url); 00820 shared_ptr<repo::Downloader> downloader_ptr; 00821 00822 if ( repokind.toEnum() == RepoType::RPMMD_e ) 00823 downloader_ptr.reset(new yum::Downloader(info, mediarootpath)); 00824 else 00825 downloader_ptr.reset( new susetags::Downloader(info, mediarootpath)); 00826 00827 RepoStatus newstatus = downloader_ptr->status(media); 00828 bool refresh = false; 00829 if ( oldstatus.checksum() == newstatus.checksum() ) 00830 { 00831 MIL << "repo has not changed" << endl; 00832 if ( policy == RefreshForced ) 00833 { 00834 MIL << "refresh set to forced" << endl; 00835 refresh = true; 00836 } 00837 } 00838 else 00839 { 00840 MIL << "repo has changed, going to refresh" << endl; 00841 refresh = true; 00842 } 00843 00844 if (!refresh) 00845 touchIndexFile(info); 00846 00847 return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE; 00848 } 00849 else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e ) 00850 { 00851 MediaMounter media( url ); 00852 RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) ); 00853 bool refresh = false; 00854 if ( oldstatus.checksum() == newstatus.checksum() ) 00855 { 00856 MIL << "repo has not changed" << endl; 00857 if ( policy == RefreshForced ) 00858 { 00859 MIL << "refresh set to forced" << endl; 00860 refresh = true; 00861 } 00862 } 00863 else 00864 { 00865 MIL << "repo has changed, going to refresh" << endl; 00866 refresh = true; 00867 } 00868 00869 if (!refresh) 00870 touchIndexFile(info); 00871 00872 return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE; 00873 } 00874 else 00875 { 00876 ZYPP_THROW(RepoUnknownTypeException(info)); 00877 } 00878 } 00879 catch ( const Exception &e ) 00880 { 00881 ZYPP_CAUGHT(e); 00882 ERR << "refresh check failed for " << url << endl; 00883 ZYPP_RETHROW(e); 00884 } 00885 00886 return REFRESH_NEEDED; // default 00887 } 00888 00889 void RepoManager::refreshMetadata( const RepoInfo &info, 00890 RawMetadataRefreshPolicy policy, 00891 const ProgressData::ReceiverFnc & progress ) 00892 { 00893 assert_alias(info); 00894 assert_urls(info); 00895 00896 // we will throw this later if no URL checks out fine 00897 RepoException rexception(_("Valid metadata not found at specified URL(s)")); 00898 00899 // try urls one by one 00900 for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it ) 00901 { 00902 try 00903 { 00904 Url url(*it); 00905 00906 // check whether to refresh metadata 00907 // if the check fails for this url, it throws, so another url will be checked 00908 if (checkIfToRefreshMetadata(info, url, policy)!=REFRESH_NEEDED) 00909 return; 00910 00911 MIL << "Going to refresh metadata from " << url << endl; 00912 00913 repo::RepoType repokind = info.type(); 00914 00915 // if the type is unknown, try probing. 00916 switch ( repokind.toEnum() ) 00917 { 00918 case RepoType::NONE_e: 00919 // unknown, probe it 00920 repokind = probe( *it, info.path() ); 00921 00922 if (repokind.toEnum() != RepoType::NONE_e) 00923 { 00924 // Adjust the probed type in RepoInfo 00925 info.setProbedType( repokind ); // lazy init! 00926 //save probed type only for repos in system 00927 for_( it, repoBegin(), repoEnd() ) 00928 { 00929 if ( info.alias() == (*it).alias() ) 00930 { 00931 RepoInfo modifiedrepo = info; 00932 modifiedrepo.setType( repokind ); 00933 modifyRepository( info.alias(), modifiedrepo ); 00934 break; 00935 } 00936 } 00937 } 00938 break; 00939 default: 00940 break; 00941 } 00942 00943 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info ); 00944 filesystem::assert_dir(mediarootpath); 00945 00946 // create temp dir as sibling of mediarootpath 00947 filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) ); 00948 00949 if ( ( repokind.toEnum() == RepoType::RPMMD_e ) || 00950 ( repokind.toEnum() == RepoType::YAST2_e ) ) 00951 { 00952 MediaSetAccess media(url); 00953 shared_ptr<repo::Downloader> downloader_ptr; 00954 00955 MIL << "Creating downloader for [ " << info.alias() << " ]" << endl; 00956 00957 if ( repokind.toEnum() == RepoType::RPMMD_e ) 00958 downloader_ptr.reset(new yum::Downloader(info, mediarootpath)); 00959 else 00960 downloader_ptr.reset( new susetags::Downloader(info, mediarootpath) ); 00961 00968 for_( it, repoBegin(), repoEnd() ) 00969 { 00970 Pathname cachepath(rawcache_path_for_repoinfo( _pimpl->options, *it )); 00971 if ( PathInfo(cachepath).isExist() ) 00972 downloader_ptr->addCachePath(cachepath); 00973 } 00974 00975 downloader_ptr->download( media, tmpdir.path() ); 00976 } 00977 else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e ) 00978 { 00979 MediaMounter media( url ); 00980 RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) ); 00981 00982 Pathname productpath( tmpdir.path() / info.path() ); 00983 filesystem::assert_dir( productpath ); 00984 std::ofstream file( (productpath/"cookie").c_str() ); 00985 if ( !file ) 00986 { 00987 // TranslatorExplanation '%s' is a filename 00988 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), (productpath/"cookie").c_str() ))); 00989 } 00990 file << url; 00991 if ( ! info.path().empty() && info.path() != "/" ) 00992 file << " (" << info.path() << ")"; 00993 file << endl; 00994 file << newstatus.checksum() << endl; 00995 00996 file.close(); 00997 } 00998 else 00999 { 01000 ZYPP_THROW(RepoUnknownTypeException()); 01001 } 01002 01003 // ok we have the metadata, now exchange 01004 // the contents 01005 filesystem::exchange( tmpdir.path(), mediarootpath ); 01006 01007 // we are done. 01008 return; 01009 } 01010 catch ( const Exception &e ) 01011 { 01012 ZYPP_CAUGHT(e); 01013 ERR << "Trying another url..." << endl; 01014 01015 // remember the exception caught for the *first URL* 01016 // if all other URLs fail, the rexception will be thrown with the 01017 // cause of the problem of the first URL remembered 01018 if (it == info.baseUrlsBegin()) 01019 rexception.remember(e); 01020 } 01021 } // for every url 01022 ERR << "No more urls..." << endl; 01023 ZYPP_THROW(rexception); 01024 } 01025 01027 01028 void RepoManager::cleanMetadata( const RepoInfo &info, 01029 const ProgressData::ReceiverFnc & progressfnc ) 01030 { 01031 ProgressData progress(100); 01032 progress.sendTo(progressfnc); 01033 01034 filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info)); 01035 progress.toMax(); 01036 } 01037 01038 void RepoManager::cleanPackages( const RepoInfo &info, 01039 const ProgressData::ReceiverFnc & progressfnc ) 01040 { 01041 ProgressData progress(100); 01042 progress.sendTo(progressfnc); 01043 01044 filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_pimpl->options, info)); 01045 progress.toMax(); 01046 } 01047 01048 void RepoManager::buildCache( const RepoInfo &info, 01049 CacheBuildPolicy policy, 01050 const ProgressData::ReceiverFnc & progressrcv ) 01051 { 01052 assert_alias(info); 01053 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info ); 01054 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info ); 01055 01056 filesystem::assert_dir(_pimpl->options.repoCachePath); 01057 RepoStatus raw_metadata_status = metadataStatus(info); 01058 if ( raw_metadata_status.empty() ) 01059 { 01060 /* if there is no cache at this point, we refresh the raw 01061 in case this is the first time - if it's !autorefresh, 01062 we may still refresh */ 01063 refreshMetadata(info, RefreshIfNeeded, progressrcv ); 01064 raw_metadata_status = metadataStatus(info); 01065 } 01066 01067 bool needs_cleaning = false; 01068 if ( isCached( info ) ) 01069 { 01070 MIL << info.alias() << " is already cached." << endl; 01071 RepoStatus cache_status = cacheStatus(info); 01072 01073 if ( cache_status.checksum() == raw_metadata_status.checksum() ) 01074 { 01075 MIL << info.alias() << " cache is up to date with metadata." << endl; 01076 if ( policy == BuildIfNeeded ) { 01077 return; 01078 } 01079 else { 01080 MIL << info.alias() << " cache rebuild is forced" << endl; 01081 } 01082 } 01083 01084 needs_cleaning = true; 01085 } 01086 01087 ProgressData progress(100); 01088 callback::SendReport<ProgressReport> report; 01089 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) ); 01090 progress.name(str::form(_("Building repository '%s' cache"), info.label().c_str())); 01091 progress.toMin(); 01092 01093 if (needs_cleaning) 01094 { 01095 cleanCache(info); 01096 } 01097 01098 MIL << info.alias() << " building cache..." << info.type() << endl; 01099 01100 Pathname base = solv_path_for_repoinfo( _pimpl->options, info); 01101 filesystem::assert_dir(base); 01102 Pathname solvfile = base / "solv"; 01103 01104 // do we have type? 01105 repo::RepoType repokind = info.type(); 01106 01107 // if the type is unknown, try probing. 01108 switch ( repokind.toEnum() ) 01109 { 01110 case RepoType::NONE_e: 01111 // unknown, probe the local metadata 01112 repokind = probe( productdatapath.asUrl() ); 01113 break; 01114 default: 01115 break; 01116 } 01117 01118 MIL << "repo type is " << repokind << endl; 01119 01120 switch ( repokind.toEnum() ) 01121 { 01122 case RepoType::RPMMD_e : 01123 case RepoType::YAST2_e : 01124 case RepoType::RPMPLAINDIR_e : 01125 { 01126 // Take care we unlink the solvfile on exception 01127 ManagedFile guard( solvfile, filesystem::unlink ); 01128 scoped_ptr<MediaMounter> forPlainDirs; 01129 01130 ExternalProgram::Arguments cmd; 01131 cmd.push_back( "repo2solv.sh" ); 01132 01133 // repo2solv expects -o as 1st arg! 01134 cmd.push_back( "-o" ); 01135 cmd.push_back( solvfile.asString() ); 01136 01137 if ( repokind == RepoType::RPMPLAINDIR ) 01138 { 01139 forPlainDirs.reset( new MediaMounter( *info.baseUrlsBegin() ) ); 01140 // recusive for plaindir as 2nd arg! 01141 cmd.push_back( "-R" ); 01142 // FIXME this does only work form dir: URLs 01143 cmd.push_back( forPlainDirs->getPathName( info.path() ).c_str() ); 01144 } 01145 else 01146 cmd.push_back( productdatapath.asString() ); 01147 01148 ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout ); 01149 std::string errdetail; 01150 01151 for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) { 01152 WAR << " " << output; 01153 if ( errdetail.empty() ) { 01154 errdetail = prog.command(); 01155 errdetail += '\n'; 01156 } 01157 errdetail += output; 01158 } 01159 01160 int ret = prog.close(); 01161 if ( ret != 0 ) 01162 { 01163 RepoException ex(str::form( _("Failed to cache repo (%d)."), ret )); 01164 ex.remember( errdetail ); 01165 ZYPP_THROW(ex); 01166 } 01167 01168 // We keep it. 01169 guard.resetDispose(); 01170 } 01171 break; 01172 default: 01173 ZYPP_THROW(RepoUnknownTypeException( _("Unhandled repository type") )); 01174 break; 01175 } 01176 // update timestamp and checksum 01177 setCacheStatus(info, raw_metadata_status); 01178 MIL << "Commit cache.." << endl; 01179 progress.toMax(); 01180 } 01181 01183 01184 repo::RepoType RepoManager::probe( const Url & url ) const 01185 { return probe( url, Pathname() ); } 01186 01187 repo::RepoType RepoManager::probe( const Url & url, const Pathname & path ) const 01188 { 01189 MIL << "going to probe the repo type at " << url << " (" << path << ")" << endl; 01190 01191 if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName()/path ).isDir() ) 01192 { 01193 // Handle non existing local directory in advance, as 01194 // MediaSetAccess does not support it. 01195 MIL << "Probed type NONE (not exists) at " << url << " (" << path << ")" << endl; 01196 return repo::RepoType::NONE; 01197 } 01198 01199 // prepare exception to be thrown if the type could not be determined 01200 // due to a media exception. We can't throw right away, because of some 01201 // problems with proxy servers returning an incorrect error 01202 // on ftp file-not-found(bnc #335906). Instead we'll check another types 01203 // before throwing. 01204 01205 // TranslatorExplanation '%s' is an URL 01206 RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() )); 01207 bool gotMediaException = false; 01208 try 01209 { 01210 MediaSetAccess access(url); 01211 try 01212 { 01213 if ( access.doesFileExist(path/"/repodata/repomd.xml") ) 01214 { 01215 MIL << "Probed type RPMMD at " << url << " (" << path << ")" << endl; 01216 return repo::RepoType::RPMMD; 01217 } 01218 } 01219 catch ( const media::MediaException &e ) 01220 { 01221 ZYPP_CAUGHT(e); 01222 DBG << "problem checking for repodata/repomd.xml file" << endl; 01223 enew.remember(e); 01224 gotMediaException = true; 01225 } 01226 01227 try 01228 { 01229 if ( access.doesFileExist(path/"/content") ) 01230 { 01231 MIL << "Probed type YAST2 at " << url << " (" << path << ")" << endl; 01232 return repo::RepoType::YAST2; 01233 } 01234 } 01235 catch ( const media::MediaException &e ) 01236 { 01237 ZYPP_CAUGHT(e); 01238 DBG << "problem checking for content file" << endl; 01239 enew.remember(e); 01240 gotMediaException = true; 01241 } 01242 01243 // if it is a non-downloading URL denoting a directory 01244 if ( ! url.schemeIsDownloading() ) 01245 { 01246 MediaMounter media( url ); 01247 if ( PathInfo(media.getPathName()/path).isDir() ) 01248 { 01249 // allow empty dirs for now 01250 MIL << "Probed type RPMPLAINDIR at " << url << " (" << path << ")" << endl; 01251 return repo::RepoType::RPMPLAINDIR; 01252 } 01253 } 01254 } 01255 catch ( const Exception &e ) 01256 { 01257 ZYPP_CAUGHT(e); 01258 // TranslatorExplanation '%s' is an URL 01259 Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() )); 01260 enew.remember(e); 01261 ZYPP_THROW(enew); 01262 } 01263 01264 if (gotMediaException) 01265 ZYPP_THROW(enew); 01266 01267 MIL << "Probed type NONE at " << url << " (" << path << ")" << endl; 01268 return repo::RepoType::NONE; 01269 } 01270 01272 01273 void RepoManager::cleanCacheDirGarbage( const ProgressData::ReceiverFnc & progressrcv ) 01274 { 01275 MIL << "Going to clean up garbage in cache dirs" << endl; 01276 01277 ProgressData progress(300); 01278 progress.sendTo(progressrcv); 01279 progress.toMin(); 01280 01281 std::list<Pathname> cachedirs; 01282 cachedirs.push_back(_pimpl->options.repoRawCachePath); 01283 cachedirs.push_back(_pimpl->options.repoPackagesCachePath); 01284 cachedirs.push_back(_pimpl->options.repoSolvCachePath); 01285 01286 for_( dir, cachedirs.begin(), cachedirs.end() ) 01287 { 01288 if ( PathInfo(*dir).isExist() ) 01289 { 01290 std::list<Pathname> entries; 01291 if ( filesystem::readdir( entries, *dir, false ) != 0 ) 01292 // TranslatorExplanation '%s' is a pathname 01293 ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir->c_str()))); 01294 01295 unsigned sdircount = entries.size(); 01296 unsigned sdircurrent = 1; 01297 for_( subdir, entries.begin(), entries.end() ) 01298 { 01299 // if it does not belong known repo, make it disappear 01300 bool found = false; 01301 for_( r, repoBegin(), repoEnd() ) 01302 if ( subdir->basename() == r->escaped_alias() ) 01303 { found = true; break; } 01304 01305 if ( ! found ) 01306 filesystem::recursive_rmdir( *subdir ); 01307 01308 progress.set( progress.val() + sdircurrent * 100 / sdircount ); 01309 ++sdircurrent; 01310 } 01311 } 01312 else 01313 progress.set( progress.val() + 100 ); 01314 } 01315 progress.toMax(); 01316 } 01317 01319 01320 void RepoManager::cleanCache( const RepoInfo &info, 01321 const ProgressData::ReceiverFnc & progressrcv ) 01322 { 01323 ProgressData progress(100); 01324 progress.sendTo(progressrcv); 01325 progress.toMin(); 01326 01327 MIL << "Removing raw metadata cache for " << info.alias() << endl; 01328 filesystem::recursive_rmdir(solv_path_for_repoinfo(_pimpl->options, info)); 01329 01330 progress.toMax(); 01331 } 01332 01334 01335 bool RepoManager::isCached( const RepoInfo &info ) const 01336 { 01337 return PathInfo(solv_path_for_repoinfo( _pimpl->options, info ) / "solv").isExist(); 01338 } 01339 01340 RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const 01341 { 01342 01343 Pathname cookiefile = solv_path_for_repoinfo(_pimpl->options, info) / "cookie"; 01344 01345 return RepoStatus::fromCookieFile(cookiefile); 01346 } 01347 01348 void RepoManager::setCacheStatus( const RepoInfo &info, const RepoStatus &status ) 01349 { 01350 Pathname base = solv_path_for_repoinfo(_pimpl->options, info); 01351 filesystem::assert_dir(base); 01352 Pathname cookiefile = base / "cookie"; 01353 01354 status.saveToCookieFile(cookiefile); 01355 } 01356 01357 void RepoManager::loadFromCache( const RepoInfo & info, 01358 const ProgressData::ReceiverFnc & progressrcv ) 01359 { 01360 assert_alias(info); 01361 Pathname solvfile = solv_path_for_repoinfo(_pimpl->options, info) / "solv"; 01362 01363 if ( ! PathInfo(solvfile).isExist() ) 01364 ZYPP_THROW(RepoNotCachedException(info)); 01365 01366 try 01367 { 01368 Repository repo = sat::Pool::instance().addRepoSolv( solvfile, info ); 01369 // test toolversion in order to rebuild solv file in case 01370 // it was written by an old satsolver-tool parser. 01371 // 01372 // Known version strings used: 01373 // - <no string> 01374 // - "1.0" 01375 // 01376 sat::LookupRepoAttr toolversion( sat::SolvAttr::repositoryToolVersion, repo ); 01377 if ( toolversion.begin().asString().empty() ) 01378 { 01379 repo.eraseFromPool(); 01380 ZYPP_THROW(Exception("Solv-file was created by old parser.")); 01381 } 01382 // else: up-to-date (or even newer). 01383 } 01384 catch ( const Exception & exp ) 01385 { 01386 ZYPP_CAUGHT( exp ); 01387 MIL << "Try to handle exception by rebuilding the solv-file" << endl; 01388 cleanCache( info, progressrcv ); 01389 buildCache( info, BuildIfNeeded, progressrcv ); 01390 01391 sat::Pool::instance().addRepoSolv( solvfile, info ); 01392 } 01393 } 01394 01396 01397 void RepoManager::addRepository( const RepoInfo &info, 01398 const ProgressData::ReceiverFnc & progressrcv ) 01399 { 01400 assert_alias(info); 01401 01402 ProgressData progress(100); 01403 callback::SendReport<ProgressReport> report; 01404 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) ); 01405 progress.name(str::form(_("Adding repository '%s'"), info.label().c_str())); 01406 progress.toMin(); 01407 01408 MIL << "Try adding repo " << info << endl; 01409 01410 RepoInfo tosave = info; 01411 if(_pimpl->repos.find(tosave)!= _pimpl->repos.end()) 01412 ZYPP_THROW(RepoAlreadyExistsException(info)); 01413 01414 // check the first url for now 01415 if ( _pimpl->options.probe ) 01416 { 01417 DBG << "unknown repository type, probing" << endl; 01418 01419 RepoType probedtype; 01420 probedtype = probe( *tosave.baseUrlsBegin(), info.path() ); 01421 if ( tosave.baseUrlsSize() > 0 ) 01422 { 01423 if ( probedtype == RepoType::NONE ) 01424 ZYPP_THROW(RepoUnknownTypeException()); 01425 else 01426 tosave.setType(probedtype); 01427 } 01428 } 01429 01430 progress.set(50); 01431 01432 // assert the directory exists 01433 filesystem::assert_dir(_pimpl->options.knownReposPath); 01434 01435 Pathname repofile = _pimpl->generateNonExistingName( 01436 _pimpl->options.knownReposPath, _pimpl->generateFilename(tosave)); 01437 // now we have a filename that does not exists 01438 MIL << "Saving repo in " << repofile << endl; 01439 01440 std::ofstream file(repofile.c_str()); 01441 if (!file) 01442 { 01443 // TranslatorExplanation '%s' is a filename 01444 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() ))); 01445 } 01446 01447 tosave.dumpAsIniOn(file); 01448 tosave.setFilepath(repofile); 01449 tosave.setMetadataPath( metadataPath( tosave ) ); 01450 tosave.setPackagesPath( packagesPath( tosave ) ); 01451 { 01452 // We chould fix the API as we must injet those paths 01453 // into the repoinfo in order to keep it usable. 01454 RepoInfo & oinfo( const_cast<RepoInfo &>(info) ); 01455 oinfo.setMetadataPath( metadataPath( tosave ) ); 01456 oinfo.setPackagesPath( packagesPath( tosave ) ); 01457 } 01458 _pimpl->repos.insert(tosave); 01459 01460 progress.set(90); 01461 01462 // check for credentials in Urls 01463 bool havePasswords = false; 01464 for_( urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd() ) 01465 if ( urlit->hasCredentialsInAuthority() ) 01466 { 01467 havePasswords = true; 01468 break; 01469 } 01470 // save the credentials 01471 if ( havePasswords ) 01472 { 01473 media::CredentialManager cm( 01474 media::CredManagerOptions(_pimpl->options.rootDir) ); 01475 01476 for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd()) 01477 if (urlit->hasCredentialsInAuthority()) 01479 cm.saveInUser(media::AuthData(*urlit)); 01480 } 01481 01482 HistoryLog().addRepository(tosave); 01483 01484 progress.toMax(); 01485 MIL << "done" << endl; 01486 } 01487 01488 void RepoManager::addRepositories( const Url &url, 01489 const ProgressData::ReceiverFnc & progressrcv ) 01490 { 01491 std::list<RepoInfo> repos = readRepoFile(url); 01492 for ( std::list<RepoInfo>::const_iterator it = repos.begin(); 01493 it != repos.end(); 01494 ++it ) 01495 { 01496 // look if the alias is in the known repos. 01497 for_ ( kit, repoBegin(), repoEnd() ) 01498 { 01499 if ( (*it).alias() == (*kit).alias() ) 01500 { 01501 ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl; 01502 ZYPP_THROW(RepoAlreadyExistsException(*it)); 01503 } 01504 } 01505 } 01506 01507 std::string filename = Pathname(url.getPathName()).basename(); 01508 01509 if ( filename == Pathname() ) 01510 { 01511 // TranslatorExplanation '%s' is an URL 01512 ZYPP_THROW(RepoException(str::form( _("Invalid repo file name at '%s'"), url.asString().c_str() ))); 01513 } 01514 01515 // assert the directory exists 01516 filesystem::assert_dir(_pimpl->options.knownReposPath); 01517 01518 Pathname repofile = _pimpl->generateNonExistingName(_pimpl->options.knownReposPath, filename); 01519 // now we have a filename that does not exists 01520 MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl; 01521 01522 std::ofstream file(repofile.c_str()); 01523 if (!file) 01524 { 01525 // TranslatorExplanation '%s' is a filename 01526 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() ))); 01527 } 01528 01529 for ( std::list<RepoInfo>::iterator it = repos.begin(); 01530 it != repos.end(); 01531 ++it ) 01532 { 01533 MIL << "Saving " << (*it).alias() << endl; 01534 it->setFilepath(repofile.asString()); 01535 it->dumpAsIniOn(file); 01536 _pimpl->repos.insert(*it); 01537 01538 HistoryLog(_pimpl->options.rootDir).addRepository(*it); 01539 } 01540 01541 MIL << "done" << endl; 01542 } 01543 01545 01546 void RepoManager::removeRepository( const RepoInfo & info, 01547 const ProgressData::ReceiverFnc & progressrcv) 01548 { 01549 ProgressData progress; 01550 callback::SendReport<ProgressReport> report; 01551 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) ); 01552 progress.name(str::form(_("Removing repository '%s'"), info.label().c_str())); 01553 01554 MIL << "Going to delete repo " << info.alias() << endl; 01555 01556 for_( it, repoBegin(), repoEnd() ) 01557 { 01558 // they can be the same only if the provided is empty, that means 01559 // the provided repo has no alias 01560 // then skip 01561 if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) ) 01562 continue; 01563 01564 // TODO match by url 01565 01566 // we have a matcing repository, now we need to know 01567 // where it does come from. 01568 RepoInfo todelete = *it; 01569 if (todelete.filepath().empty()) 01570 { 01571 ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") )); 01572 } 01573 else 01574 { 01575 // figure how many repos are there in the file: 01576 std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath()); 01577 if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) ) 01578 { 01579 // easy, only this one, just delete the file 01580 if ( filesystem::unlink(todelete.filepath()) != 0 ) 01581 { 01582 // TranslatorExplanation '%s' is a filename 01583 ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), todelete.filepath().c_str() ))); 01584 } 01585 MIL << todelete.alias() << " sucessfully deleted." << endl; 01586 } 01587 else 01588 { 01589 // there are more repos in the same file 01590 // write them back except the deleted one. 01591 //TmpFile tmp; 01592 //std::ofstream file(tmp.path().c_str()); 01593 01594 // assert the directory exists 01595 filesystem::assert_dir(todelete.filepath().dirname()); 01596 01597 std::ofstream file(todelete.filepath().c_str()); 01598 if (!file) 01599 { 01600 // TranslatorExplanation '%s' is a filename 01601 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), todelete.filepath().c_str() ))); 01602 } 01603 for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin(); 01604 fit != filerepos.end(); 01605 ++fit ) 01606 { 01607 if ( (*fit).alias() != todelete.alias() ) 01608 (*fit).dumpAsIniOn(file); 01609 } 01610 } 01611 01612 CombinedProgressData subprogrcv(progress, 70); 01613 CombinedProgressData cleansubprogrcv(progress, 30); 01614 // now delete it from cache 01615 if ( isCached(todelete) ) 01616 cleanCache( todelete, subprogrcv); 01617 // now delete metadata (#301037) 01618 cleanMetadata( todelete, cleansubprogrcv); 01619 _pimpl->repos.erase(todelete); 01620 MIL << todelete.alias() << " sucessfully deleted." << endl; 01621 HistoryLog(_pimpl->options.rootDir).removeRepository(todelete); 01622 return; 01623 } // else filepath is empty 01624 01625 } 01626 // should not be reached on a sucess workflow 01627 ZYPP_THROW(RepoNotFoundException(info)); 01628 } 01629 01631 01632 void RepoManager::modifyRepository( const std::string &alias, 01633 const RepoInfo & newinfo_r, 01634 const ProgressData::ReceiverFnc & progressrcv ) 01635 { 01636 RepoInfo toedit = getRepositoryInfo(alias); 01637 RepoInfo newinfo( newinfo_r ); // need writable copy to upadte housekeeping data 01638 01639 // check if the new alias already exists when renaming the repo 01640 if ( alias != newinfo.alias() && hasRepo( newinfo.alias() ) ) 01641 { 01642 ZYPP_THROW(RepoAlreadyExistsException(newinfo)); 01643 } 01644 01645 if (toedit.filepath().empty()) 01646 { 01647 ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") )); 01648 } 01649 else 01650 { 01651 // figure how many repos are there in the file: 01652 std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath()); 01653 01654 // there are more repos in the same file 01655 // write them back except the deleted one. 01656 //TmpFile tmp; 01657 //std::ofstream file(tmp.path().c_str()); 01658 01659 // assert the directory exists 01660 filesystem::assert_dir(toedit.filepath().dirname()); 01661 01662 std::ofstream file(toedit.filepath().c_str()); 01663 if (!file) 01664 { 01665 // TranslatorExplanation '%s' is a filename 01666 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), toedit.filepath().c_str() ))); 01667 } 01668 for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin(); 01669 fit != filerepos.end(); 01670 ++fit ) 01671 { 01672 // if the alias is different, dump the original 01673 // if it is the same, dump the provided one 01674 if ( (*fit).alias() != toedit.alias() ) 01675 (*fit).dumpAsIniOn(file); 01676 else 01677 newinfo.dumpAsIniOn(file); 01678 } 01679 01680 newinfo.setFilepath(toedit.filepath()); 01681 _pimpl->repos.erase(toedit); 01682 _pimpl->repos.insert(newinfo); 01683 HistoryLog(_pimpl->options.rootDir).modifyRepository(toedit, newinfo); 01684 MIL << "repo " << alias << " modified" << endl; 01685 } 01686 } 01687 01689 01690 RepoInfo RepoManager::getRepositoryInfo( const std::string &alias, 01691 const ProgressData::ReceiverFnc & progressrcv ) 01692 { 01693 RepoInfo info; 01694 info.setAlias(alias); 01695 RepoConstIterator it = _pimpl->repos.find( info ); 01696 if( it == repoEnd() ) 01697 ZYPP_THROW(RepoNotFoundException(info)); 01698 else 01699 return *it; 01700 } 01701 01703 01704 RepoInfo RepoManager::getRepositoryInfo( const Url & url, 01705 const url::ViewOption & urlview, 01706 const ProgressData::ReceiverFnc & progressrcv ) 01707 { 01708 for_( it, repoBegin(), repoEnd() ) 01709 { 01710 for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin(); 01711 urlit != (*it).baseUrlsEnd(); 01712 ++urlit) 01713 { 01714 if ((*urlit).asString(urlview) == url.asString(urlview)) 01715 return *it; 01716 } 01717 } 01718 RepoInfo info; 01719 info.setBaseUrl(url); 01720 ZYPP_THROW(RepoNotFoundException(info)); 01721 } 01722 01724 // 01725 // Services 01726 // 01728 01729 bool RepoManager::serviceEmpty() const 01730 { return _pimpl->services.empty(); } 01731 01732 RepoManager::ServiceSizeType RepoManager::serviceSize() const 01733 { return _pimpl->services.size(); } 01734 01735 RepoManager::ServiceConstIterator RepoManager::serviceBegin() const 01736 { return _pimpl->services.begin(); } 01737 01738 RepoManager::ServiceConstIterator RepoManager::serviceEnd() const 01739 { return _pimpl->services.end(); } 01740 01741 ServiceInfo RepoManager::getService( const std::string & alias ) const 01742 { 01743 for_( it, serviceBegin(), serviceEnd() ) 01744 if ( it->alias() == alias ) 01745 return *it; 01746 return ServiceInfo::noService; 01747 } 01748 01749 bool RepoManager::hasService( const std::string & alias ) const 01750 { 01751 for_( it, serviceBegin(), serviceEnd() ) 01752 if ( it->alias() == alias ) 01753 return true; 01754 return false; 01755 } 01756 01758 01759 void RepoManager::addService( const std::string & alias, const Url & url ) 01760 { 01761 addService( ServiceInfo(alias, url) ); 01762 } 01763 01764 void RepoManager::addService( const ServiceInfo & service ) 01765 { 01766 assert_alias( service ); 01767 01768 // check if service already exists 01769 if ( hasService( service.alias() ) ) 01770 ZYPP_THROW( ServiceAlreadyExistsException( service ) ); 01771 01772 // Writable ServiceInfo is needed to save the location 01773 // of the .service file. Finaly insert into the service list. 01774 ServiceInfo toSave( service ); 01775 _pimpl->saveService( toSave ); 01776 _pimpl->services.insert( toSave ); 01777 01778 // check for credentials in Url (username:password, not ?credentials param) 01779 if ( toSave.url().hasCredentialsInAuthority() ) 01780 { 01781 media::CredentialManager cm( 01782 media::CredManagerOptions(_pimpl->options.rootDir) ); 01783 01785 cm.saveInUser(media::AuthData(toSave.url())); 01786 } 01787 01788 MIL << "added service " << toSave.alias() << endl; 01789 } 01790 01792 01793 void RepoManager::removeService( const std::string & alias ) 01794 { 01795 MIL << "Going to delete repo " << alias << endl; 01796 01797 const ServiceInfo & service = getService( alias ); 01798 01799 Pathname location = service.filepath(); 01800 if( location.empty() ) 01801 { 01802 ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") )); 01803 } 01804 01805 ServiceSet tmpSet; 01806 parser::ServiceFileReader( location, ServiceCollector(tmpSet) ); 01807 01808 // only one service definition in the file 01809 if ( tmpSet.size() == 1 ) 01810 { 01811 if ( filesystem::unlink(location) != 0 ) 01812 { 01813 // TranslatorExplanation '%s' is a filename 01814 ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), location.c_str() ))); 01815 } 01816 MIL << alias << " sucessfully deleted." << endl; 01817 } 01818 else 01819 { 01820 filesystem::assert_dir(location.dirname()); 01821 01822 std::ofstream file(location.c_str()); 01823 if( !file ) 01824 { 01825 // TranslatorExplanation '%s' is a filename 01826 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), location.c_str() ))); 01827 } 01828 01829 for_(it, tmpSet.begin(), tmpSet.end()) 01830 { 01831 if( it->alias() != alias ) 01832 it->dumpAsIniOn(file); 01833 } 01834 01835 MIL << alias << " sucessfully deleted from file " << location << endl; 01836 } 01837 01838 // now remove all repositories added by this service 01839 RepoCollector rcollector; 01840 getRepositoriesInService( alias, 01841 boost::make_function_output_iterator( 01842 bind( &RepoCollector::collect, &rcollector, _1 ) ) ); 01843 // cannot do this directly in getRepositoriesInService - would invalidate iterators 01844 for_(rit, rcollector.repos.begin(), rcollector.repos.end()) 01845 removeRepository(*rit); 01846 } 01847 01848 void RepoManager::removeService( const ServiceInfo & service ) 01849 { removeService(service.alias()); } 01850 01852 01853 void RepoManager::refreshServices() 01854 { 01855 // copy the set of services since refreshService 01856 // can eventually invalidate the iterator 01857 ServiceSet services( serviceBegin(), serviceEnd() ); 01858 for_( it, services.begin(), services.end() ) 01859 { 01860 if ( !it->enabled() ) 01861 continue; 01862 01863 try { 01864 refreshService(*it); 01865 } 01866 catch ( const repo::ServicePluginInformalException & e ) 01867 { ;/* ignore ServicePluginInformalException */ } 01868 } 01869 } 01870 01871 void RepoManager::refreshService( const ServiceInfo & service ) 01872 { refreshService( service.alias() ); } 01873 01874 void RepoManager::refreshService( const std::string & alias ) 01875 { 01876 ServiceInfo service( getService( alias ) ); 01877 assert_alias( service ); 01878 assert_url( service ); 01879 // NOTE: It might be necessary to modify and rewrite the service info. 01880 // Either when probing the type, or when adjusting the repositories 01881 // enable/disable state.: 01882 bool serviceModified = false; 01883 MIL << "Going to refresh service '" << service.alias() << "', url: "<< service.url() << endl; 01884 01886 01887 // if the type is unknown, try probing. 01888 if ( service.type() == repo::ServiceType::NONE ) 01889 { 01890 repo::ServiceType type = probeService( service.url() ); 01891 if ( type != ServiceType::NONE ) 01892 { 01893 service.setProbedType( type ); // lazy init! 01894 serviceModified = true; 01895 } 01896 } 01897 01898 // get target distro identifier 01899 std::string servicesTargetDistro = _pimpl->options.servicesTargetDistro; 01900 if ( servicesTargetDistro.empty() ) 01901 { 01902 servicesTargetDistro = Target::targetDistribution( Pathname() ); 01903 } 01904 DBG << "ServicesTargetDistro: " << servicesTargetDistro << endl; 01905 01906 // parse it 01907 RepoCollector collector(servicesTargetDistro); 01908 // FIXME Ugly hack: ServiceRepos may throw ServicePluginInformalException 01909 // which is actually a notification. Using an exception for this 01910 // instead of signal/callback is bad. Needs to be fixed here, in refreshServices() 01911 // and in zypper. 01912 std::pair<DefaultIntegral<bool,false>, repo::ServicePluginInformalException> uglyHack; 01913 try { 01914 ServiceRepos repos(service, bind( &RepoCollector::collect, &collector, _1 )); 01915 } 01916 catch ( const repo::ServicePluginInformalException & e ) 01917 { 01918 /* ignore ServicePluginInformalException and throw later */ 01919 uglyHack.first = true; 01920 uglyHack.second = e; 01921 } 01922 01923 // set service alias and base url for all collected repositories 01924 for_( it, collector.repos.begin(), collector.repos.end() ) 01925 { 01926 // if the repo url was not set by the repoindex parser, set service's url 01927 Url url; 01928 01929 if ( it->baseUrlsEmpty() ) 01930 url = service.url(); 01931 else 01932 { 01933 // service repo can contain only one URL now, so no need to iterate. 01934 url = *it->baseUrlsBegin(); 01935 } 01936 01937 // libzypp currently has problem with separate url + path handling 01938 // so just append the path to the baseurl 01939 if ( !it->path().empty() ) 01940 { 01941 Pathname path(url.getPathName()); 01942 path /= it->path(); 01943 url.setPathName( path.asString() ); 01944 it->setPath(""); 01945 } 01946 01947 // Prepend service alias: 01948 it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) ); 01949 01950 // save the url 01951 it->setBaseUrl( url ); 01952 // set refrence to the parent service 01953 it->setService( service.alias() ); 01954 } 01955 01957 // Now compare collected repos with the ones in the system... 01958 // 01959 RepoInfoList oldRepos; 01960 getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) ); 01961 01962 // find old repositories to remove... 01963 for_( it, oldRepos.begin(), oldRepos.end() ) 01964 { 01965 if ( ! foundAliasIn( it->alias(), collector.repos ) ) 01966 { 01967 if ( it->enabled() && ! service.repoToDisableFind( it->alias() ) ) 01968 { 01969 DBG << "Service removes enabled repo " << it->alias() << endl; 01970 service.addRepoToEnable( it->alias() ); 01971 serviceModified = true; 01972 } 01973 else 01974 { 01975 DBG << "Service removes disabled repo " << it->alias() << endl; 01976 } 01977 removeRepository( *it ); 01978 } 01979 } 01980 01982 // create missing repositories and modify exising ones if needed... 01983 for_( it, collector.repos.begin(), collector.repos.end() ) 01984 { 01985 // Service explicitly requests the repo being enabled? 01986 // Service explicitly requests the repo being disabled? 01987 // And hopefully not both ;) If so, enable wins. 01988 bool beEnabled = service.repoToEnableFind( it->alias() ); 01989 bool beDisabled = service.repoToDisableFind( it->alias() ); 01990 01991 // Make sure the service repo is created with the 01992 // appropriate enable 01993 if ( beEnabled ) it->setEnabled(true); 01994 if ( beDisabled ) it->setEnabled(false); 01995 01996 if ( beEnabled ) 01997 { 01998 // Remove from enable request list. 01999 // NOTE: repoToDisable is handled differently. 02000 // It gets cleared on each refresh. 02001 service.delRepoToEnable( it->alias() ); 02002 serviceModified = true; 02003 } 02004 02005 RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) ); 02006 if ( oldRepo == oldRepos.end() ) 02007 { 02008 // Not found in oldRepos ==> a new repo to add 02009 02010 // At that point check whether a repo with the same alias 02011 // exists outside this service. Maybe forcefully re-alias 02012 // the existing repo? 02013 DBG << "Service adds repo " << it->alias() << " " << (it->enabled()?"enabled":"disabled") << endl; 02014 addRepository( *it ); 02015 02016 // save repo credentials 02017 // ma@: task for modifyRepository? 02018 } 02019 else 02020 { 02021 // ==> an exising repo to check 02022 bool oldRepoModified = false; 02023 02024 // changed enable? 02025 if ( beEnabled ) 02026 { 02027 if ( ! oldRepo->enabled() ) 02028 { 02029 DBG << "Service repo " << it->alias() << " gets enabled" << endl; 02030 oldRepo->setEnabled( true ); 02031 oldRepoModified = true; 02032 } 02033 else 02034 { 02035 DBG << "Service repo " << it->alias() << " stays enabled" << endl; 02036 } 02037 } 02038 else if ( beDisabled ) 02039 { 02040 if ( oldRepo->enabled() ) 02041 { 02042 DBG << "Service repo " << it->alias() << " gets disabled" << endl; 02043 oldRepo->setEnabled( false ); 02044 oldRepoModified = true; 02045 } 02046 else 02047 { 02048 DBG << "Service repo " << it->alias() << " stays disabled" << endl; 02049 } 02050 } 02051 else 02052 { 02053 DBG << "Service repo " << it->alias() << " stays " << (oldRepo->enabled()?"enabled":"disabled") << endl; 02054 } 02055 02056 // changed url? 02057 // service repo can contain only one URL now, so no need to iterate. 02058 if ( oldRepo->url() != it->url() ) 02059 { 02060 DBG << "Service repo " << it->alias() << " gets new URL " << it->url() << endl; 02061 oldRepo->setBaseUrl( it->url() ); 02062 oldRepoModified = true; 02063 } 02064 02065 // save if modified: 02066 if ( oldRepoModified ) 02067 { 02068 modifyRepository( oldRepo->alias(), *oldRepo ); 02069 } 02070 } 02071 } 02072 02073 // Unlike reposToEnable, reposToDisable is always cleared after refresh. 02074 if ( ! service.reposToDisableEmpty() ) 02075 { 02076 service.clearReposToDisable(); 02077 serviceModified = true; 02078 } 02079 02081 // save service if modified: 02082 if ( serviceModified ) 02083 { 02084 // write out modified service file. 02085 modifyService( service.alias(), service ); 02086 } 02087 02088 if ( uglyHack.first ) 02089 { 02090 throw( uglyHack.second ); // intentionally not ZYPP_THROW 02091 } 02092 } 02093 02095 02096 void RepoManager::modifyService(const std::string & oldAlias, const ServiceInfo & newService) 02097 { 02098 MIL << "Going to modify service " << oldAlias << endl; 02099 02100 // we need a writable copy to link it to the file where 02101 // it is saved if we modify it 02102 ServiceInfo service(newService); 02103 02104 if ( service.type() == ServiceType::PLUGIN ) 02105 { 02106 MIL << "Not modifying plugin service '" << oldAlias << "'" << endl; 02107 return; 02108 } 02109 02110 const ServiceInfo & oldService = getService(oldAlias); 02111 02112 Pathname location = oldService.filepath(); 02113 if( location.empty() ) 02114 { 02115 ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") )); 02116 } 02117 02118 // remember: there may multiple services being defined in one file: 02119 ServiceSet tmpSet; 02120 parser::ServiceFileReader( location, ServiceCollector(tmpSet) ); 02121 02122 filesystem::assert_dir(location.dirname()); 02123 std::ofstream file(location.c_str()); 02124 for_(it, tmpSet.begin(), tmpSet.end()) 02125 { 02126 if( *it != oldAlias ) 02127 it->dumpAsIniOn(file); 02128 } 02129 service.dumpAsIniOn(file); 02130 file.close(); 02131 service.setFilepath(location); 02132 02133 _pimpl->services.erase(oldAlias); 02134 _pimpl->services.insert(service); 02135 02136 // changed properties affecting also repositories 02137 if( oldAlias != service.alias() // changed alias 02138 || oldService.enabled() != service.enabled() // changed enabled status 02139 ) 02140 { 02141 std::vector<RepoInfo> toModify; 02142 getRepositoriesInService(oldAlias, std::back_inserter(toModify)); 02143 for_( it, toModify.begin(), toModify.end() ) 02144 { 02145 if (oldService.enabled() && !service.enabled()) 02146 it->setEnabled(false); 02147 else if (!oldService.enabled() && service.enabled()) 02148 { 02151 } 02152 else 02153 it->setService(service.alias()); 02154 modifyRepository(it->alias(), *it); 02155 } 02156 } 02157 02159 } 02160 02162 02163 repo::ServiceType RepoManager::probeService( const Url &url ) const 02164 { 02165 try 02166 { 02167 MediaSetAccess access(url); 02168 if ( access.doesFileExist("/repo/repoindex.xml") ) 02169 return repo::ServiceType::RIS; 02170 } 02171 catch ( const media::MediaException &e ) 02172 { 02173 ZYPP_CAUGHT(e); 02174 // TranslatorExplanation '%s' is an URL 02175 RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() )); 02176 enew.remember(e); 02177 ZYPP_THROW(enew); 02178 } 02179 catch ( const Exception &e ) 02180 { 02181 ZYPP_CAUGHT(e); 02182 // TranslatorExplanation '%s' is an URL 02183 Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() )); 02184 enew.remember(e); 02185 ZYPP_THROW(enew); 02186 } 02187 02188 return repo::ServiceType::NONE; 02189 } 02190 02192 02193 std::ostream & operator<<( std::ostream & str, const RepoManager & obj ) 02194 { 02195 return str << *obj._pimpl; 02196 } 02197 02199 } // namespace zypp