1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/repo/PackageProvider.cc
15 #include <zypp/repo/PackageDelta.h>
16 #include <zypp/base/Logger.h>
17 #include <zypp/base/Gettext.h>
18 #include <zypp/base/UserRequestException.h>
19 #include <zypp/base/NonCopyable.h>
20 #include <zypp/repo/PackageProvider.h>
21 #include <zypp/repo/Applydeltarpm.h>
22 #include <zypp/repo/PackageDelta.h>
24 #include <zypp/TmpPath.h>
25 #include <zypp/ZConfig.h>
26 #include <zypp/RepoInfo.h>
27 #include <zypp/RepoManager.h>
28 #include <zypp/SrcPackage.h>
30 #include <zypp/ZYppFactory.h>
31 #include <zypp/Target.h>
32 #include <zypp/target/rpm/RpmDb.h>
33 #include <zypp/FileChecker.h>
34 #include <zypp/target/rpm/RpmHeader.h>
38 ///////////////////////////////////////////////////////////////////
41 ///////////////////////////////////////////////////////////////////
44 ///////////////////////////////////////////////////////////////////
45 /// \class RpmSigCheckException
46 /// \brief Exception thrown by \ref PackageProviderImpl::rpmSigFileChecker
47 ///////////////////////////////////////////////////////////////////
48 class RpmSigCheckException : public FileCheckException
51 RpmSigCheckException( repo::DownloadResolvableReport::Action action_r, std::string msg_r = "RpmSigCheckException" )
52 : FileCheckException( std::move(msg_r) )
53 , _action( std::move(action_r) )
56 /** Users final decision how to proceed */
57 const repo::DownloadResolvableReport::Action & action() const
61 repo::DownloadResolvableReport::Action _action;
65 ///////////////////////////////////////////////////////////////////
66 // class PackageProviderPolicy
67 ///////////////////////////////////////////////////////////////////
69 bool PackageProviderPolicy::queryInstalled( const std::string & name_r,
71 const Arch & arch_r ) const
73 if ( _queryInstalledCB )
74 return _queryInstalledCB( name_r, ed_r, arch_r );
78 ///////////////////////////////////////////////////////////////////
79 /// \class PackageProvider::Impl
80 /// \brief PackageProvider implementation interface.
81 ///////////////////////////////////////////////////////////////////
82 struct PackageProvider::Impl : private base::NonCopyable
87 /** Provide the package.
91 virtual ManagedFile providePackage() const = 0;
93 /** Provide the package if it is cached. */
94 virtual ManagedFile providePackageFromCache() const = 0;
96 /** Whether the package is cached. */
97 virtual bool isCached() const = 0;
100 ///////////////////////////////////////////////////////////////////
101 /// \class PackageProviderImpl<TPackage>
102 /// \brief PackageProvider implementation for \c Package and \c SrcPackage
103 ///////////////////////////////////////////////////////////////////
104 template <class TPackage>
105 class PackageProviderImpl : public PackageProvider::Impl
107 typedef typename TPackage::constPtr TPackagePtr; // Package or SrcPackage
108 typedef callback::UserData UserData;
110 /** Ctor taking the Package to provide. */
111 PackageProviderImpl( RepoMediaAccess & access_r, const TPackagePtr & package_r,
112 const PackageProviderPolicy & policy_r )
113 : _policy( policy_r )
114 , _package( package_r )
115 , _access( access_r )
119 virtual ~PackageProviderImpl() {}
122 /** Provide the package.
123 * The basic workflow.
126 virtual ManagedFile providePackage() const;
128 /** Provide the package if it is cached. */
129 virtual ManagedFile providePackageFromCache() const
131 ManagedFile ret( doProvidePackageFromCache() );
132 if ( ! ( ret->empty() || _package->repoInfo().keepPackages() ) )
133 ret.setDispose( filesystem::unlink );
137 /** Whether the package is cached. */
138 virtual bool isCached() const
139 { return ! doProvidePackageFromCache()->empty(); }
142 typedef PackageProviderImpl<TPackage> Base;
143 typedef callback::SendReport<repo::DownloadResolvableReport> Report;
145 /** Lookup the final rpm in cache.
147 * A cache hit will return a non empty ManagedFile and an empty one on cache miss.
149 * \note File disposal depending on the repos keepPackages setting
150 * are not set here, but in \ref providePackage or \ref providePackageFromCache.
152 ManagedFile doProvidePackageFromCache() const
153 { return ManagedFile( _package->cachedLocation() ); }
155 /** Actually provide the final rpm.
156 * Report start/problem/finish and retry loop are hadled by \ref providePackage.
157 * Here you trigger just progress and delta/plugin callbacks as needed.
159 * Proxy method for progressPackageDownload is provided here.
161 * ProvideFilePolicy policy;
162 * policy.progressCB( bind( &Base::progressPackageDownload, this, _1 ) );
163 * return _access.provideFile( _package->repoInfo(), loc, policy );
166 * \note The provided default implementation retrieves the packages default
169 virtual ManagedFile doProvidePackage() const
172 OnMediaLocation loc = _package->location();
174 ProvideFilePolicy policy;
175 policy.progressCB( bind( &Base::progressPackageDownload, this, _1 ) );
176 policy.fileChecker( bind( &Base::rpmSigFileChecker, this, _1 ) );
177 return _access.provideFile( _package->repoInfo(), loc, policy );
181 /** Access to the DownloadResolvableReport */
182 Report & report() const
185 /** Redirect ProvideFilePolicy package download progress to this. */
186 bool progressPackageDownload( int value ) const
187 { return report()->progress( value, _package ); }
190 /** \name Validate a rpm packages signature.
192 * This is the \ref FileChecker passed down to the \ref Fetcher to validate
193 * a provided rpm package. This builtin checker includes the workflow
194 * communicating with the user in case of a problem with the package
197 * \throws RpmSigCheckException if the package is not accepted, propagating
198 * the users decision how to proceed (\ref DownloadResolvableReport::Action).
200 * \note This check is also needed, if the the rpm is built locally by using
201 * delta rpms! \ref \see RpmPackageProvider
204 void rpmSigFileChecker( const Pathname & file_r ) const
206 RepoInfo info = _package->repoInfo();
207 if ( info.pkgGpgCheck() )
209 UserData userData( "pkgGpgCheck" );
210 ResObject::constPtr roptr( _package ); // gcc6 needs it more explcit. Has problem deducing
211 userData.set( "ResObject", roptr ); // a type for '_package->asKind<ResObject>()'...
212 /*legacy:*/userData.set( "Package", roptr->asKind<Package>() );
213 userData.set( "Localpath", file_r );
215 RpmDb::CheckPackageResult res = RpmDb::CHK_NOKEY;
216 while ( res == RpmDb::CHK_NOKEY ) {
217 res = packageSigCheck( file_r, info.pkgGpgCheckIsMandatory(), userData );
219 // publish the checkresult, even if it is OK. Apps may want to report something...
220 report()->pkgGpgCheck( userData );
222 if ( res == RpmDb::CHK_NOKEY ) {
223 // if the check fails because we don't know the key
224 // we try to resolv it with gpgkey urls from the
225 // repository, if available
227 target::rpm::RpmHeader::constPtr hr = target::rpm::RpmHeader::readPackage( file_r );
229 // we did not find any information about the key in the header
230 // this should never happen
231 WAR << "Unable to read package header from " << hr << endl;
235 std::string keyID = hr->signatureKeyID();
236 if ( keyID.length() > 0 ) {
237 if ( ! getZYpp()->keyRing()->provideAndImportKeyFromRepositoryWorkflow( keyID, info ) )
241 // we did not find any information about the key in the header
242 // this should never happen
243 WAR << "packageSigCheck returned without setting providing missing key information" << endl;
249 if ( res != RpmDb::CHK_OK )
251 if ( userData.hasvalue( "Action" ) ) // pkgGpgCheck report provided an user error action
253 resolveSignatureErrorAction( userData.get( "Action", repo::DownloadResolvableReport::ABORT ) );
255 else if ( userData.haskey( "Action" ) ) // pkgGpgCheck requests the default problem report (wo. details)
257 defaultReportSignatureError( res );
259 else // no advice from user => usedefaults
263 case RpmDb::CHK_OK: // Signature is OK
266 case RpmDb::CHK_NOKEY: // Public key is unavailable
267 case RpmDb::CHK_NOTFOUND: // Signature is unknown type
268 case RpmDb::CHK_FAIL: // Signature does not verify
269 case RpmDb::CHK_NOTTRUSTED: // Signature is OK, but key is not trusted
270 case RpmDb::CHK_ERROR: // File does not exist or can't be opened
271 case RpmDb::CHK_NOSIG: // File is unsigned
273 // report problem (w. details), throw if to abort, else retry/ignore
274 defaultReportSignatureError( res, str::Str() << userData.get<RpmDb::CheckPackageDetail>( "CheckPackageDetail" ) );
282 typedef target::rpm::RpmDb RpmDb;
284 /** Actual rpm package signature check. */
285 RpmDb::CheckPackageResult packageSigCheck( const Pathname & path_r, bool isMandatory_r, UserData & userData ) const
288 _target = getZYpp()->getTarget();
290 RpmDb::CheckPackageResult ret = RpmDb::CHK_ERROR;
291 RpmDb::CheckPackageDetail detail;
294 ret = _target->rpmDb().checkPackageSignature( path_r, detail );
295 if ( ret == RpmDb::CHK_NOSIG && !isMandatory_r )
297 WAR << "Relax CHK_NOSIG: Config says unsigned packages are OK" << endl;
302 detail.push_back( RpmDb::CheckPackageDetail::value_type( ret, "OOps. Target is not initialized!" ) );
304 userData.set( "CheckPackageResult", ret );
305 userData.set( "CheckPackageDetail", std::move(detail) );
309 /** React on signature verification error user action.
310 * \note: IGNORE == accept insecure file (no SkipRequestException!)
312 void resolveSignatureErrorAction( repo::DownloadResolvableReport::Action action_r ) const
316 case repo::DownloadResolvableReport::IGNORE:
317 WAR << _package->asUserString() << ": " << "User requested to accept insecure file" << endl;
320 case repo::DownloadResolvableReport::RETRY:
321 case repo::DownloadResolvableReport::ABORT:
322 ZYPP_THROW(RpmSigCheckException(action_r,"Signature verification failed"));
327 /** Default signature verification error handling. */
328 void defaultReportSignatureError( RpmDb::CheckPackageResult ret, const std::string & detail_r = std::string() ) const
331 msg << _package->asUserString() << ": " << _("Signature verification failed") << " " << ret;
332 if ( ! detail_r.empty() )
333 msg << "\n" << detail_r;
334 resolveSignatureErrorAction( report()->problem( _package, repo::DownloadResolvableReport::INVALID, msg.str() ) );
339 PackageProviderPolicy _policy;
340 TPackagePtr _package;
341 RepoMediaAccess & _access;
344 typedef shared_ptr<void> ScopedGuard;
346 ScopedGuard newReport() const
348 _report.reset( new Report );
349 // Use a custom deleter calling _report.reset() when guard goes out of
350 // scope (cast required as reset is overloaded). We want report to end
351 // when leaving providePackage and not wait for *this going out of scope.
352 return shared_ptr<void>( static_cast<void*>(0),
353 bind( mem_fun_ref( static_cast<void (shared_ptr<Report>::*)()>(&shared_ptr<Report>::reset) ),
358 mutable shared_ptr<Report> _report;
359 mutable Target_Ptr _target;
361 ///////////////////////////////////////////////////////////////////
363 template <class TPackage>
364 ManagedFile PackageProviderImpl<TPackage>::providePackage() const
366 ScopedGuard guardReport( newReport() );
368 // check for cache hit:
369 ManagedFile ret( providePackageFromCache() );
370 if ( ! ret->empty() )
372 MIL << "provided Package from cache " << _package << " at " << ret << endl;
373 report()->infoInCache( _package, ret );
374 return ret; // <-- cache hit
377 // HERE: cache misss, check toplevel cache or do download:
378 RepoInfo info = _package->repoInfo();
380 // Check toplevel cache
382 RepoManagerOptions topCache;
383 if ( info.packagesPath().dirname() != topCache.repoPackagesCachePath ) // not using toplevel cache
385 const OnMediaLocation & loc( _package->location() );
386 if ( ! loc.checksum().empty() ) // no cache hit without checksum
388 PathInfo pi( topCache.repoPackagesCachePath / info.packagesPath().basename() / info.path() / loc.filename() );
389 if ( pi.isExist() && loc.checksum() == CheckSum( loc.checksum().type(), std::ifstream( pi.c_str() ) ) )
391 report()->start( _package, pi.path().asFileUrl() );
392 const Pathname & dest( info.packagesPath() / info.path() / loc.filename() );
393 if ( filesystem::assert_dir( dest.dirname() ) == 0 && filesystem::hardlinkCopy( pi.path(), dest ) == 0 )
395 ret = ManagedFile( dest );
396 if ( ! info.keepPackages() )
397 ret.setDispose( filesystem::unlink );
399 MIL << "provided Package from toplevel cache " << _package << " at " << ret << endl;
400 report()->finish( _package, repo::DownloadResolvableReport::NO_ERROR, std::string() );
401 return ret; // <-- toplevel cache hit
408 // FIXME we only support the first url for now.
409 if ( info.baseUrlsEmpty() )
410 ZYPP_THROW(Exception("No url in repository."));
412 MIL << "provide Package " << _package << endl;
413 Url url = * info.baseUrlsBegin();
417 if ( ! ret->empty() )
419 ret.setDispose( filesystem::unlink );
422 report()->start( _package, url );
425 ret = doProvidePackage();
427 catch ( const UserRequestException & excpt )
429 ERR << "Failed to provide Package " << _package << endl;
431 ZYPP_RETHROW( excpt );
433 catch ( const RpmSigCheckException & excpt )
435 ERR << "Failed to provide Package " << _package << endl;
438 // Signature verification error was already reported by the
439 // rpmSigFileChecker. Just handle the users action decision:
440 switch ( excpt.action() )
442 case repo::DownloadResolvableReport::RETRY:
445 case repo::DownloadResolvableReport::IGNORE:
446 ZYPP_THROW(SkipRequestException("User requested skip of corrupted file"));
449 case repo::DownloadResolvableReport::ABORT:
450 ZYPP_THROW(AbortRequestException("User requested to abort"));
455 catch ( const FileCheckException & excpt )
457 ERR << "Failed to provide Package " << _package << endl;
460 const std::string & package_str = _package->asUserString();
461 // TranslatorExplanation %s = package being checked for integrity
462 switch ( report()->problem( _package, repo::DownloadResolvableReport::INVALID, str::form(_("Package %s seems to be corrupted during transfer. Do you want to retry retrieval?"), package_str.c_str() ) ) )
464 case repo::DownloadResolvableReport::RETRY:
467 case repo::DownloadResolvableReport::IGNORE:
468 ZYPP_THROW(SkipRequestException("User requested skip of corrupted file"));
471 case repo::DownloadResolvableReport::ABORT:
472 ZYPP_THROW(AbortRequestException("User requested to abort"));
477 catch ( const Exception & excpt )
479 ERR << "Failed to provide Package " << _package << endl;
482 // Aything else gets reported
483 const std::string & package_str = _package->asUserString();
485 // TranslatorExplanation %s = name of the package being processed.
486 std::string detail_str( str::form(_("Failed to provide Package %s. Do you want to retry retrieval?"), package_str.c_str() ) );
487 detail_str += str::form( "\n\n%s", excpt.asUserHistory().c_str() );
489 switch ( report()->problem( _package, repo::DownloadResolvableReport::IO, detail_str.c_str() ) )
491 case repo::DownloadResolvableReport::RETRY:
494 case repo::DownloadResolvableReport::IGNORE:
495 ZYPP_THROW(SkipRequestException("User requested skip of file", excpt));
498 case repo::DownloadResolvableReport::ABORT:
499 ZYPP_THROW(AbortRequestException("User requested to abort", excpt));
506 // bsc#1045735: Be sure no invalid files stay in the cache!
507 if ( ! ret->empty() )
508 ret.setDispose( filesystem::unlink );
512 report()->finish( _package, repo::DownloadResolvableReport::NO_ERROR, std::string() );
513 MIL << "provided Package " << _package << " at " << ret << endl;
518 ///////////////////////////////////////////////////////////////////
519 /// \class RpmPackageProvider
520 /// \brief RPM PackageProvider implementation (with deltarpm processing).
521 ///////////////////////////////////////////////////////////////////
522 class RpmPackageProvider : public PackageProviderImpl<Package>
525 RpmPackageProvider( RepoMediaAccess & access_r,
526 const Package::constPtr & package_r,
527 const DeltaCandidates & deltas_r,
528 const PackageProviderPolicy & policy_r )
529 : PackageProviderImpl<Package>( access_r, package_r, policy_r )
530 , _deltas( deltas_r )
534 virtual ManagedFile doProvidePackage() const;
537 typedef packagedelta::DeltaRpm DeltaRpm;
539 ManagedFile tryDelta( const DeltaRpm & delta_r ) const;
541 bool progressDeltaDownload( int value ) const
542 { return report()->progressDeltaDownload( value ); }
544 void progressDeltaApply( int value ) const
545 { return report()->progressDeltaApply( value ); }
547 bool queryInstalled( const Edition & ed_r = Edition() ) const
548 { return _policy.queryInstalled( _package->name(), ed_r, _package->arch() ); }
551 DeltaCandidates _deltas;
553 ///////////////////////////////////////////////////////////////////
555 ManagedFile RpmPackageProvider::doProvidePackage() const
557 // check whether to process patch/delta rpms
558 // FIXME we only check the first url for now.
559 if ( ZConfig::instance().download_use_deltarpm()
560 && ( _package->repoInfo().url().schemeIsDownloading() || ZConfig::instance().download_use_deltarpm_always() ) )
562 std::list<DeltaRpm> deltaRpms;
563 _deltas.deltaRpms( _package ).swap( deltaRpms );
565 if ( ! deltaRpms.empty() && queryInstalled() && applydeltarpm::haveApplydeltarpm() )
567 for_( it, deltaRpms.begin(), deltaRpms.end())
569 DBG << "tryDelta " << *it << endl;
570 ManagedFile ret( tryDelta( *it ) );
571 if ( ! ret->empty() )
577 // no patch/delta -> provide full package
578 return Base::doProvidePackage();
581 ManagedFile RpmPackageProvider::tryDelta( const DeltaRpm & delta_r ) const
583 if ( delta_r.baseversion().edition() != Edition::noedition
584 && ! queryInstalled( delta_r.baseversion().edition() ) )
585 return ManagedFile();
587 if ( ! applydeltarpm::quickcheck( delta_r.baseversion().sequenceinfo() ) )
588 return ManagedFile();
590 report()->startDeltaDownload( delta_r.location().filename(),
591 delta_r.location().downloadSize() );
595 ProvideFilePolicy policy;
596 policy.progressCB( bind( &RpmPackageProvider::progressDeltaDownload, this, _1 ) );
597 delta = _access.provideFile( delta_r.repository().info(), delta_r.location(), policy );
599 catch ( const Exception & excpt )
601 report()->problemDeltaDownload( excpt.asUserHistory() );
602 return ManagedFile();
604 report()->finishDeltaDownload();
606 report()->startDeltaApply( delta );
607 if ( ! applydeltarpm::check( delta_r.baseversion().sequenceinfo() ) )
609 report()->problemDeltaApply( _("applydeltarpm check failed.") );
610 return ManagedFile();
614 Pathname cachedest( _package->repoInfo().packagesPath() / _package->repoInfo().path() / _package->location().filename() );
615 Pathname builddest( cachedest.extend( ".drpm" ) );
617 if ( ! applydeltarpm::provide( delta, builddest,
618 bind( &RpmPackageProvider::progressDeltaApply, this, _1 ) ) )
620 report()->problemDeltaApply( _("applydeltarpm failed.") );
621 return ManagedFile();
623 ManagedFile builddestCleanup( builddest, filesystem::unlink );
624 report()->finishDeltaApply();
626 // Check and move it into the cache
627 // Here the rpm itself is ready. If the packages sigcheck fails, it
628 // makes no sense to return a ManagedFile() and fallback to download the
629 // full rpm. It won't be different. So let the exceptions escape...
630 rpmSigFileChecker( builddest );
631 if ( filesystem::hardlinkCopy( builddest, cachedest ) != 0 )
632 ZYPP_THROW( Exception( str::Str() << "Can't hardlink/copy " << builddest << " to " << cachedest ) );
634 return ManagedFile( cachedest, filesystem::unlink );
637 ///////////////////////////////////////////////////////////////////
638 // class PackageProvider
639 ///////////////////////////////////////////////////////////////////
642 inline PackageProvider::Impl * make( RepoMediaAccess & access_r, const PoolItem & pi_r,
643 const DeltaCandidates & deltas_r,
644 const PackageProviderPolicy & policy_r )
646 if ( pi_r.isKind<Package>() )
647 return new RpmPackageProvider( access_r, pi_r->asKind<Package>(), deltas_r, policy_r );
648 else if ( pi_r.isKind<SrcPackage>() )
649 return new PackageProviderImpl<SrcPackage>( access_r, pi_r->asKind<SrcPackage>(), policy_r );
651 ZYPP_THROW( Exception( str::Str() << "Don't know how to cache non-package " << pi_r.asUserString() ) );
654 inline PackageProvider::Impl * make( RepoMediaAccess & access_r, const PoolItem & pi_r,
655 const PackageProviderPolicy & policy_r )
657 if ( pi_r.isKind<Package>() )
658 return new PackageProviderImpl<Package>( access_r, pi_r->asKind<Package>(), policy_r );
659 else if ( pi_r.isKind<SrcPackage>() )
660 return new PackageProviderImpl<SrcPackage>( access_r, pi_r->asKind<SrcPackage>(), policy_r );
662 ZYPP_THROW( Exception( str::Str() << "Don't know how to cache non-package " << pi_r.asUserString() ) );
665 inline PackageProvider::Impl * make( RepoMediaAccess & access_r, const Package::constPtr & package_r,
666 const DeltaCandidates & deltas_r,
667 const PackageProviderPolicy & policy_r )
668 { return new RpmPackageProvider( access_r, package_r, deltas_r, policy_r ); }
670 } // namespace factory
671 ///////////////////////////////////////////////////////////////////
673 PackageProvider::PackageProvider( RepoMediaAccess & access_r, const PoolItem & pi_r,
674 const DeltaCandidates & deltas_r, const PackageProviderPolicy & policy_r )
676 : _pimpl( factory::make( access_r, pi_r, deltas_r, policy_r ) )
679 PackageProvider::PackageProvider( RepoMediaAccess & access_r, const PoolItem & pi_r,
680 const PackageProviderPolicy & policy_r )
681 : _pimpl( factory::make( access_r, pi_r, policy_r ) )
685 PackageProvider::PackageProvider( RepoMediaAccess & access_r,
686 const Package::constPtr & package_r,
687 const DeltaCandidates & deltas_r,
688 const PackageProviderPolicy & policy_r )
689 : _pimpl( factory::make( access_r, package_r, deltas_r, policy_r ) )
692 PackageProvider::~PackageProvider()
695 ManagedFile PackageProvider::providePackage() const
696 { return _pimpl->providePackage(); }
698 ManagedFile PackageProvider::providePackageFromCache() const
699 { return _pimpl->providePackageFromCache(); }
701 bool PackageProvider::isCached() const
702 { return _pimpl->isCached(); }
705 ///////////////////////////////////////////////////////////////////
707 ///////////////////////////////////////////////////////////////////