Imported Upstream version 15.2.0
[platform/upstream/libzypp.git] / zypp / repo / PackageProvider.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/repo/PackageProvider.cc
10  *
11 */
12 #include <iostream>
13 #include <fstream>
14 #include <sstream>
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"
23
24 #include "zypp/TmpPath.h"
25 #include "zypp/ZConfig.h"
26 #include "zypp/RepoInfo.h"
27 #include "zypp/RepoManager.h"
28
29 #include "zypp/ZYppFactory.h"
30 #include "zypp/Target.h"
31 #include "zypp/target/rpm/RpmDb.h"
32 #include "zypp/FileChecker.h"
33
34 using std::endl;
35
36 ///////////////////////////////////////////////////////////////////
37 namespace zypp
38 {
39   ///////////////////////////////////////////////////////////////////
40   namespace repo
41   {
42     ///////////////////////////////////////////////////////////////////
43     //  class PackageProviderPolicy
44     ///////////////////////////////////////////////////////////////////
45
46     bool PackageProviderPolicy::queryInstalled( const std::string & name_r,
47                                                 const Edition &     ed_r,
48                                                 const Arch &        arch_r ) const
49     {
50       if ( _queryInstalledCB )
51         return _queryInstalledCB( name_r, ed_r, arch_r );
52       return false;
53     }
54
55
56     ///////////////////////////////////////////////////////////////////
57     /// \class PackageProvider::Impl
58     /// \brief PackageProvider implementation.
59     ///////////////////////////////////////////////////////////////////
60     class PackageProvider::Impl : private base::NonCopyable
61     {
62       typedef callback::UserData UserData;
63     public:
64       /** Ctor taking the Package to provide. */
65       Impl( RepoMediaAccess & access_r,
66             const Package::constPtr & package_r,
67             const DeltaCandidates & deltas_r,
68             const PackageProviderPolicy & policy_r )
69       : _policy( policy_r )
70       , _package( package_r )
71       , _deltas( deltas_r )
72       , _access( access_r )
73       , _retry(false)
74       {}
75
76       virtual ~Impl() {}
77
78       /** Factory method providing the appropriate implementation.
79        * Called by PackageProvider ctor. Returned pointer should be
80        * immediately wrapped into a smartpointer.
81        */
82       static Impl * factoryMake( RepoMediaAccess & access_r,
83                                  const Package::constPtr & package_r,
84                                  const DeltaCandidates & deltas_r,
85                                  const PackageProviderPolicy & policy_r );
86
87     public:
88       /** Provide the package.
89        * The basic workflow.
90        * \throws Exception.
91        */
92       ManagedFile providePackage() const;
93
94       /** Provide the package if it is cached. */
95       ManagedFile providePackageFromCache() const
96       {
97         ManagedFile ret( doProvidePackageFromCache() );
98         if ( ! ( ret->empty() ||  _package->repoInfo().keepPackages() ) )
99           ret.setDispose( filesystem::unlink );
100         return ret;
101       }
102
103       /** Whether the package is cached. */
104       bool isCached() const
105       { return ! doProvidePackageFromCache()->empty(); }
106
107     protected:
108       typedef PackageProvider::Impl     Base;
109       typedef callback::SendReport<repo::DownloadResolvableReport>      Report;
110
111       /** Lookup the final rpm in cache.
112        *
113        * A non empty ManagedFile will be returned to the caller.
114        *
115        * \note File disposal depending on the repos keepPackages setting
116        * are not set here, but in \ref providePackage or \ref providePackageFromCache.
117        *
118        * \note The provoided default implementation returns an empty ManagedFile
119        * (cache miss).
120        */
121       virtual ManagedFile doProvidePackageFromCache() const = 0;
122
123       /** Actually provide the final rpm.
124        * Report start/problem/finish and retry loop are hadled by \ref providePackage.
125        * Here you trigger just progress and delta/plugin callbacks as needed.
126        *
127        * Proxy methods for progressPackageDownload and failOnChecksum are provided here.
128        * Create similar proxies for other progress callbacks in derived classes and link
129        * it to ProvideFilePolicy for download:
130        * \code
131        * ProvideFilePolicy policy;
132        * policy.progressCB( bind( &Base::progressPackageDownload, this, _1 ) );
133        * policy.failOnChecksumErrorCB( bind( &Base::failOnChecksumError, this ) );
134        * return _access.provideFile( _package->repoInfo(), loc, policy );
135        * \endcode
136        *
137        * \note The provoided default implementation retrieves the packages default
138        * location.
139        */
140       virtual ManagedFile doProvidePackage() const = 0;
141
142     protected:
143       /** Access to the DownloadResolvableReport */
144       Report & report() const
145       { return *_report; }
146
147       /** Redirect ProvideFilePolicy package download progress to this. */
148       bool progressPackageDownload( int value ) const
149       { return report()->progress( value, _package ); }
150
151       /** Redirect ProvideFilePolicy failOnChecksumError to this if needed. */
152       bool failOnChecksumError() const
153       {
154         std::string package_str = _package->name() + "-" + _package->edition().asString();
155
156         // TranslatorExplanation %s = package being checked for integrity
157         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() ) ) )
158         {
159           case repo::DownloadResolvableReport::RETRY:
160             _retry = true;
161             break;
162           case repo::DownloadResolvableReport::IGNORE:
163             ZYPP_THROW(SkipRequestException("User requested skip of corrupted file"));
164             break;
165           case repo::DownloadResolvableReport::ABORT:
166             ZYPP_THROW(AbortRequestException("User requested to abort"));
167             break;
168           default:
169             break;
170         }
171         return true; // anyway a failure
172       }
173
174       typedef target::rpm::RpmDb RpmDb;
175
176       RpmDb::checkPackageResult packageSigCheck( const Pathname & path_r, UserData & userData ) const
177       {
178         if ( !_target )
179           _target = getZYpp()->getTarget();
180
181         RpmDb::checkPackageResult ret = RpmDb::CHK_ERROR;
182         RpmDb::CheckPackageDetail detail;
183         if ( _target )
184           ret = _target->rpmDb().checkPackage( path_r, detail );
185         else
186           detail.push_back( RpmDb::CheckPackageDetail::value_type( ret, "OOps. Target is not initialized!" ) );
187
188         userData.set( "CheckPackageResult", ret );
189         userData.set( "CheckPackageDetail", std::move(detail) );
190         return ret;
191       }
192
193       /** React on signature verrification error user action */
194       void resolveSignatureErrorAction( repo::DownloadResolvableReport::Action action_r ) const
195       {
196         // TranslatorExplanation %s = package being checked for integrity
197         switch ( action_r )
198         {
199           case repo::DownloadResolvableReport::RETRY:
200             _retry = true;
201             break;
202           case repo::DownloadResolvableReport::IGNORE:
203             WAR << _package->asUserString() << ": " << "User requested skip of insecure file" << endl;
204             break;
205           default:
206           case repo::DownloadResolvableReport::ABORT:
207             ZYPP_THROW(AbortRequestException("User requested to abort"));
208             break;
209         }
210       }
211
212       /** Default signature verrification error handling. */
213       void defaultReportSignatureError( RpmDb::checkPackageResult ret ) const
214       {
215         std::string msg( str::Str() << _package->asUserString() << ": " << _("Signature verification failed") << " " << ret );
216         resolveSignatureErrorAction( report()->problem( _package, repo::DownloadResolvableReport::INVALID, msg ) );
217       }
218
219     protected:
220       PackageProviderPolicy     _policy;
221       Package::constPtr         _package;
222       DeltaCandidates           _deltas;
223       RepoMediaAccess &         _access;
224
225     private:
226       typedef shared_ptr<void>  ScopedGuard;
227
228       ScopedGuard newReport() const
229       {
230         _report.reset( new Report );
231         // Use a custom deleter calling _report.reset() when guard goes out of
232         // scope (cast required as reset is overloaded). We want report to end
233         // when leaving providePackage and not wait for *this going out of scope.
234         return shared_ptr<void>( static_cast<void*>(0),
235                                  bind( mem_fun_ref( static_cast<void (shared_ptr<Report>::*)()>(&shared_ptr<Report>::reset) ),
236                                        ref(_report) ) );
237       }
238
239       mutable bool               _retry;
240       mutable shared_ptr<Report> _report;
241       mutable Target_Ptr         _target;
242     };
243     ///////////////////////////////////////////////////////////////////
244
245     /** Default implementation (cache miss). */
246     ManagedFile PackageProvider::Impl::doProvidePackageFromCache() const
247     { return ManagedFile(); }
248
249     /** Default implementation (provide full package) */
250     ManagedFile PackageProvider::Impl::doProvidePackage() const
251     {
252       ManagedFile ret;
253       OnMediaLocation loc = _package->location();
254
255       ProvideFilePolicy policy;
256       policy.progressCB( bind( &Base::progressPackageDownload, this, _1 ) );
257       policy.failOnChecksumErrorCB( bind( &Base::failOnChecksumError, this ) );
258       return _access.provideFile( _package->repoInfo(), loc, policy );
259     }
260
261     ///////////////////////////////////////////////////////////////////
262
263     ManagedFile PackageProvider::Impl::providePackage() const
264     {
265       ScopedGuard guardReport( newReport() );
266
267       // check for cache hit:
268       ManagedFile ret( providePackageFromCache() );
269       if ( ! ret->empty() )
270       {
271         MIL << "provided Package from cache " << _package << " at " << ret << endl;
272         report()->infoInCache( _package, ret );
273         return ret; // <-- cache hit
274       }
275
276       // HERE: cache misss, check toplevel cache or do download:
277       RepoInfo info = _package->repoInfo();
278
279       // Check toplevel cache
280       {
281         RepoManagerOptions topCache;
282         if ( info.packagesPath().dirname() != topCache.repoPackagesCachePath )  // not using toplevel cache
283         {
284           const OnMediaLocation & loc( _package->location() );
285           if ( ! loc.checksum().empty() )       // no cache hit without checksum
286           {
287             PathInfo pi( topCache.repoPackagesCachePath / info.packagesPath().basename() / loc.filename() );
288             if ( pi.isExist() && loc.checksum() == CheckSum( loc.checksum().type(), std::ifstream( pi.c_str() ) ) )
289             {
290               report()->start( _package, pi.path().asFileUrl() );
291               const Pathname & dest( info.packagesPath() / loc.filename() );
292               if ( filesystem::assert_dir( dest.dirname() ) == 0 && filesystem::hardlinkCopy( pi.path(), dest ) == 0 )
293               {
294                 ret = ManagedFile( dest );
295                 if ( ! info.keepPackages() )
296                   ret.setDispose( filesystem::unlink );
297
298                 MIL << "provided Package from toplevel cache " << _package << " at " << ret << endl;
299                 report()->finish( _package, repo::DownloadResolvableReport::NO_ERROR, std::string() );
300                 return ret; // <-- toplevel cache hit
301               }
302             }
303           }
304         }
305       }
306
307       // FIXME we only support the first url for now.
308       if ( info.baseUrlsEmpty() )
309         ZYPP_THROW(Exception("No url in repository."));
310
311       MIL << "provide Package " << _package << endl;
312       Url url = * info.baseUrlsBegin();
313       do {
314         _retry = false;
315         if ( ! ret->empty() )
316         {
317           ret.setDispose( filesystem::unlink );
318           ret.reset();
319         }
320         report()->start( _package, url );
321         try  // ELIMINATE try/catch by providing a log-guard
322           {
323             ret = doProvidePackage();
324
325             if ( info.pkgGpgCheck() )
326             {
327               UserData userData( "pkgGpgCheck" );
328               userData.set( "Package", _package );
329               userData.set( "Localpath", ret.value() );
330               RpmDb::checkPackageResult res = packageSigCheck( ret, userData );
331               // publish the checkresult, even if it is OK. Apps may want to report something...
332               report()->pkgGpgCheck( userData );
333
334               if ( res != RpmDb::CHK_OK )
335               {
336                 if ( userData.hasvalue( "Action" ) )    // pkgGpgCheck provided an user error action
337                 {
338                   resolveSignatureErrorAction( userData.get( "Action", repo::DownloadResolvableReport::ABORT ) );
339                 }
340                 else                                    // no advice from user => usedefaults
341                 {
342                   switch ( res )
343                   {
344                     case RpmDb::CHK_OK:         // Signature is OK
345                       break;
346
347                     case RpmDb::CHK_NOKEY:      // Public key is unavailable
348                     case RpmDb::CHK_NOTFOUND:   // Signature is unknown type
349                     case RpmDb::CHK_NOTTRUSTED: // Signature is OK, but key is not trusted
350                       // should fail in future versions.
351                       break;
352
353                     case RpmDb::CHK_FAIL:       // Signature does not verify
354                     case RpmDb::CHK_ERROR:      // File does not exist or can't be opened
355                       // report problem, throw if to abort, else retry/ignore
356                       defaultReportSignatureError( res );
357                       break;
358                   }
359                 }
360               }
361             }
362           }
363         catch ( const UserRequestException & excpt )
364           {
365             // UserRequestException e.g. from failOnChecksumError was already reported.
366             ERR << "Failed to provide Package " << _package << endl;
367             if ( ! _retry )
368               {
369                 ZYPP_RETHROW( excpt );
370               }
371           }
372         catch ( const Exception & excpt )
373           {
374             ERR << "Failed to provide Package " << _package << endl;
375             if ( ! _retry )
376               {
377                 // Aything else gets reported
378                 std::string package_str = _package->name() + "-" + _package->edition().asString();
379
380                 // TranslatorExplanation %s = name of the package being processed.
381                 std::string detail_str( str::form(_("Failed to provide Package %s. Do you want to retry retrieval?"), package_str.c_str() ) );
382                 detail_str += str::form( "\n\n%s", excpt.asUserHistory().c_str() );
383
384                 switch ( report()->problem( _package, repo::DownloadResolvableReport::IO, detail_str.c_str() ) )
385                 {
386                       case repo::DownloadResolvableReport::RETRY:
387                         _retry = true;
388                         break;
389                       case repo::DownloadResolvableReport::IGNORE:
390                         ZYPP_THROW(SkipRequestException("User requested skip of corrupted file", excpt));
391                         break;
392                       case repo::DownloadResolvableReport::ABORT:
393                         ZYPP_THROW(AbortRequestException("User requested to abort", excpt));
394                         break;
395                       default:
396                         ZYPP_RETHROW( excpt );
397                         break;
398                 }
399               }
400           }
401       } while ( _retry );
402
403       report()->finish( _package, repo::DownloadResolvableReport::NO_ERROR, std::string() );
404       MIL << "provided Package " << _package << " at " << ret << endl;
405       return ret;
406     }
407
408
409     ///////////////////////////////////////////////////////////////////
410     /// \class RpmPackageProvider
411     /// \brief RPM PackageProvider implementation.
412     ///////////////////////////////////////////////////////////////////
413     class RpmPackageProvider : public PackageProvider::Impl
414     {
415     public:
416       RpmPackageProvider( RepoMediaAccess & access_r,
417                           const Package::constPtr & package_r,
418                           const DeltaCandidates & deltas_r,
419                           const PackageProviderPolicy & policy_r )
420       : PackageProvider::Impl( access_r, package_r, deltas_r, policy_r )
421       {}
422
423     protected:
424       virtual ManagedFile doProvidePackageFromCache() const;
425
426       virtual ManagedFile doProvidePackage() const;
427
428     private:
429       typedef packagedelta::DeltaRpm    DeltaRpm;
430
431       ManagedFile tryDelta( const DeltaRpm & delta_r ) const;
432
433       bool progressDeltaDownload( int value ) const
434       { return report()->progressDeltaDownload( value ); }
435
436       void progressDeltaApply( int value ) const
437       { return report()->progressDeltaApply( value ); }
438
439       bool queryInstalled( const Edition & ed_r = Edition() ) const
440       { return _policy.queryInstalled( _package->name(), ed_r, _package->arch() ); }
441     };
442     ///////////////////////////////////////////////////////////////////
443
444     ManagedFile RpmPackageProvider::doProvidePackageFromCache() const
445     {
446       return ManagedFile( _package->cachedLocation() );
447     }
448
449     ManagedFile RpmPackageProvider::doProvidePackage() const
450     {
451       Url url;
452       RepoInfo info = _package->repoInfo();
453       // FIXME we only support the first url for now.
454       if ( info.baseUrlsEmpty() )
455         ZYPP_THROW(Exception("No url in repository."));
456       else
457         url = * info.baseUrlsBegin();
458
459       // check whether to process patch/delta rpms
460       if ( ZConfig::instance().download_use_deltarpm()
461         && ( url.schemeIsDownloading() || ZConfig::instance().download_use_deltarpm_always() ) )
462       {
463         std::list<DeltaRpm> deltaRpms;
464         _deltas.deltaRpms( _package ).swap( deltaRpms );
465
466         if ( ! deltaRpms.empty() && queryInstalled() && applydeltarpm::haveApplydeltarpm() )
467         {
468           for_( it, deltaRpms.begin(), deltaRpms.end())
469           {
470             DBG << "tryDelta " << *it << endl;
471             ManagedFile ret( tryDelta( *it ) );
472             if ( ! ret->empty() )
473               return ret;
474           }
475         }
476       }
477
478       // no patch/delta -> provide full package
479       return Base::doProvidePackage();
480     }
481
482     ManagedFile RpmPackageProvider::tryDelta( const DeltaRpm & delta_r ) const
483     {
484       if ( delta_r.baseversion().edition() != Edition::noedition
485            && ! queryInstalled( delta_r.baseversion().edition() ) )
486         return ManagedFile();
487
488       if ( ! applydeltarpm::quickcheck( delta_r.baseversion().sequenceinfo() ) )
489         return ManagedFile();
490
491       report()->startDeltaDownload( delta_r.location().filename(),
492                                     delta_r.location().downloadSize() );
493       ManagedFile delta;
494       try
495         {
496           ProvideFilePolicy policy;
497           policy.progressCB( bind( &RpmPackageProvider::progressDeltaDownload, this, _1 ) );
498           delta = _access.provideFile( delta_r.repository().info(), delta_r.location(), policy );
499         }
500       catch ( const Exception & excpt )
501         {
502           report()->problemDeltaDownload( excpt.asUserHistory() );
503           return ManagedFile();
504         }
505       report()->finishDeltaDownload();
506
507       report()->startDeltaApply( delta );
508       if ( ! applydeltarpm::check( delta_r.baseversion().sequenceinfo() ) )
509         {
510           report()->problemDeltaApply( _("applydeltarpm check failed.") );
511           return ManagedFile();
512         }
513
514       // build the package and put it into the cache
515       Pathname destination( _package->repoInfo().packagesPath() / _package->location().filename() );
516
517       if ( ! applydeltarpm::provide( delta, destination,
518                                      bind( &RpmPackageProvider::progressDeltaApply, this, _1 ) ) )
519         {
520           report()->problemDeltaApply( _("applydeltarpm failed.") );
521           return ManagedFile();
522         }
523       report()->finishDeltaApply();
524
525       return ManagedFile( destination, filesystem::unlink );
526     }
527
528 #if 0
529     ///////////////////////////////////////////////////////////////////
530     /// \class PluginPackageProvider
531     /// \brief Plugin PackageProvider implementation.
532     ///
533     /// Basically downloads the default package and calls a
534     /// 'stem'2rpm plugin to cteate the final .rpm package.
535     ///////////////////////////////////////////////////////////////////
536     class PluginPackageProvider : public PackageProvider::Impl
537     {
538     public:
539       PluginPackageProvider( const std::string & stem_r,
540                              RepoMediaAccess & access_r,
541                              const Package::constPtr & package_r,
542                              const DeltaCandidates & deltas_r,
543                              const PackageProviderPolicy & policy_r )
544       : Base( access_r, package_r, deltas_r, policy_r )
545       {}
546
547     protected:
548       virtual ManagedFile doProvidePackageFromCache() const
549       {
550         return Base::doProvidePackageFromCache();
551       }
552
553       virtual ManagedFile doProvidePackage() const
554       {
555         return Base::doProvidePackage();
556       }
557     };
558     ///////////////////////////////////////////////////////////////////
559 #endif
560
561     ///////////////////////////////////////////////////////////////////
562     //  class PackageProvider
563     ///////////////////////////////////////////////////////////////////
564
565     PackageProvider::Impl * PackageProvider::Impl::factoryMake( RepoMediaAccess & access_r,
566                                                                 const Package::constPtr & package_r,
567                                                                 const DeltaCandidates & deltas_r,
568                                                                 const PackageProviderPolicy & policy_r )
569     {
570       return new RpmPackageProvider( access_r, package_r, deltas_r, policy_r );
571     }
572
573     PackageProvider::PackageProvider( RepoMediaAccess & access_r,
574                                       const Package::constPtr & package_r,
575                                       const DeltaCandidates & deltas_r,
576                                       const PackageProviderPolicy & policy_r )
577     : _pimpl( Impl::factoryMake( access_r, package_r, deltas_r, policy_r ) )
578     {}
579
580     PackageProvider::~PackageProvider()
581     {}
582
583     ManagedFile PackageProvider::providePackage() const
584     { return _pimpl->providePackage(); }
585
586     ManagedFile PackageProvider::providePackageFromCache() const
587     { return _pimpl->providePackageFromCache(); }
588
589     bool PackageProvider::isCached() const
590     { return _pimpl->isCached(); }
591
592   } // namespace repo
593   ///////////////////////////////////////////////////////////////////
594 } // namespace zypp
595 ///////////////////////////////////////////////////////////////////