Imported Upstream version 14.45.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 method for progressPackageDownload is provided here.
128        * \code
129        * ProvideFilePolicy policy;
130        * policy.progressCB( bind( &Base::progressPackageDownload, this, _1 ) );
131        * return _access.provideFile( _package->repoInfo(), loc, policy );
132        * \endcode
133        *
134        * \note The provided default implementation retrieves the packages default
135        * location.
136        */
137       virtual ManagedFile doProvidePackage() const = 0;
138
139     protected:
140       /** Access to the DownloadResolvableReport */
141       Report & report() const
142       { return *_report; }
143
144       /** Redirect ProvideFilePolicy package download progress to this. */
145       bool progressPackageDownload( int value ) const
146       { return report()->progress( value, _package ); }
147
148       typedef target::rpm::RpmDb RpmDb;
149
150       RpmDb::CheckPackageResult packageSigCheck( const Pathname & path_r, UserData & userData ) const
151       {
152         if ( !_target )
153           _target = getZYpp()->getTarget();
154
155         RpmDb::CheckPackageResult ret = RpmDb::CHK_ERROR;
156         RpmDb::CheckPackageDetail detail;
157         if ( _target )
158           ret = _target->rpmDb().checkPackage( path_r, detail );
159         else
160           detail.push_back( RpmDb::CheckPackageDetail::value_type( ret, "OOps. Target is not initialized!" ) );
161
162         userData.set( "CheckPackageResult", ret );
163         userData.set( "CheckPackageDetail", std::move(detail) );
164         return ret;
165       }
166
167       /** React on signature verification error user action
168        * \note: IGNORE == accept insecure file (no SkipRequestException!)
169        */
170       void resolveSignatureErrorAction( repo::DownloadResolvableReport::Action action_r ) const
171       {
172         switch ( action_r )
173         {
174           case repo::DownloadResolvableReport::RETRY:
175             _retry = true;
176             break;
177           case repo::DownloadResolvableReport::IGNORE:
178             WAR << _package->asUserString() << ": " << "User requested to accept insecure file" << endl;
179             break;
180           default:
181           case repo::DownloadResolvableReport::ABORT:
182             ZYPP_THROW(AbortRequestException("User requested to abort"));
183             break;
184         }
185       }
186
187       /** Default signature verification error handling. */
188       void defaultReportSignatureError( RpmDb::CheckPackageResult ret, const std::string & detail_r = std::string() ) const
189       {
190         str::Str msg;
191         msg << _package->asUserString() << ": " << _("Signature verification failed") << " " << ret;
192         if ( ! detail_r.empty() )
193           msg << "\n" << detail_r;
194         resolveSignatureErrorAction( report()->problem( _package, repo::DownloadResolvableReport::INVALID, msg.str() ) );
195       }
196
197     protected:
198       PackageProviderPolicy     _policy;
199       Package::constPtr         _package;
200       DeltaCandidates           _deltas;
201       RepoMediaAccess &         _access;
202
203     private:
204       typedef shared_ptr<void>  ScopedGuard;
205
206       ScopedGuard newReport() const
207       {
208         _report.reset( new Report );
209         // Use a custom deleter calling _report.reset() when guard goes out of
210         // scope (cast required as reset is overloaded). We want report to end
211         // when leaving providePackage and not wait for *this going out of scope.
212         return shared_ptr<void>( static_cast<void*>(0),
213                                  bind( mem_fun_ref( static_cast<void (shared_ptr<Report>::*)()>(&shared_ptr<Report>::reset) ),
214                                        ref(_report) ) );
215       }
216
217       mutable bool               _retry;
218       mutable shared_ptr<Report> _report;
219       mutable Target_Ptr         _target;
220     };
221     ///////////////////////////////////////////////////////////////////
222
223     /** Default implementation (cache miss). */
224     ManagedFile PackageProvider::Impl::doProvidePackageFromCache() const
225     { return ManagedFile(); }
226
227     /** Default implementation (provide full package) */
228     ManagedFile PackageProvider::Impl::doProvidePackage() const
229     {
230       ManagedFile ret;
231       OnMediaLocation loc = _package->location();
232
233       ProvideFilePolicy policy;
234       policy.progressCB( bind( &Base::progressPackageDownload, this, _1 ) );
235       return _access.provideFile( _package->repoInfo(), loc, policy );
236     }
237
238     ///////////////////////////////////////////////////////////////////
239
240     ManagedFile PackageProvider::Impl::providePackage() const
241     {
242       ScopedGuard guardReport( newReport() );
243
244       // check for cache hit:
245       ManagedFile ret( providePackageFromCache() );
246       if ( ! ret->empty() )
247       {
248         MIL << "provided Package from cache " << _package << " at " << ret << endl;
249         report()->infoInCache( _package, ret );
250         return ret; // <-- cache hit
251       }
252
253       // HERE: cache misss, check toplevel cache or do download:
254       RepoInfo info = _package->repoInfo();
255
256       // Check toplevel cache
257       {
258         RepoManagerOptions topCache;
259         if ( info.packagesPath().dirname() != topCache.repoPackagesCachePath )  // not using toplevel cache
260         {
261           const OnMediaLocation & loc( _package->location() );
262           if ( ! loc.checksum().empty() )       // no cache hit without checksum
263           {
264             PathInfo pi( topCache.repoPackagesCachePath / info.packagesPath().basename() / loc.filename() );
265             if ( pi.isExist() && loc.checksum() == CheckSum( loc.checksum().type(), std::ifstream( pi.c_str() ) ) )
266             {
267               report()->start( _package, pi.path().asFileUrl() );
268               const Pathname & dest( info.packagesPath() / loc.filename() );
269               if ( filesystem::assert_dir( dest.dirname() ) == 0 && filesystem::hardlinkCopy( pi.path(), dest ) == 0 )
270               {
271                 ret = ManagedFile( dest );
272                 if ( ! info.keepPackages() )
273                   ret.setDispose( filesystem::unlink );
274
275                 MIL << "provided Package from toplevel cache " << _package << " at " << ret << endl;
276                 report()->finish( _package, repo::DownloadResolvableReport::NO_ERROR, std::string() );
277                 return ret; // <-- toplevel cache hit
278               }
279             }
280           }
281         }
282       }
283
284       // FIXME we only support the first url for now.
285       if ( info.baseUrlsEmpty() )
286         ZYPP_THROW(Exception("No url in repository."));
287
288       MIL << "provide Package " << _package << endl;
289       Url url = * info.baseUrlsBegin();
290       do {
291         _retry = false;
292         if ( ! ret->empty() )
293         {
294           ret.setDispose( filesystem::unlink );
295           ret.reset();
296         }
297         report()->start( _package, url );
298         try
299           {
300             ret = doProvidePackage();
301
302             if ( info.pkgGpgCheck() )
303             {
304               UserData userData( "pkgGpgCheck" );
305               userData.set( "Package", _package );
306               userData.set( "Localpath", ret.value() );
307               RpmDb::CheckPackageResult res = packageSigCheck( ret, userData );
308               // publish the checkresult, even if it is OK. Apps may want to report something...
309               report()->pkgGpgCheck( userData );
310 USR << "CHK: " << res << endl;
311               if ( res != RpmDb::CHK_OK )
312               {
313                 if ( userData.hasvalue( "Action" ) )    // pkgGpgCheck report provided an user error action
314                 {
315                   resolveSignatureErrorAction( userData.get( "Action", repo::DownloadResolvableReport::ABORT ) );
316                 }
317                 else if ( userData.haskey( "Action" ) ) // pkgGpgCheck requests the default problem report (wo. details)
318                 {
319                   defaultReportSignatureError( res );
320                 }
321                 else                                    // no advice from user => usedefaults
322                 {
323                   switch ( res )
324                   {
325                     case RpmDb::CHK_OK:         // Signature is OK
326                       break;
327
328                     case RpmDb::CHK_NOKEY:      // Public key is unavailable
329                     case RpmDb::CHK_NOTFOUND:   // Signature is unknown type
330                     case RpmDb::CHK_FAIL:       // Signature does not verify
331                     case RpmDb::CHK_NOTTRUSTED: // Signature is OK, but key is not trusted
332                     case RpmDb::CHK_ERROR:      // File does not exist or can't be opened
333                     default:
334                       // report problem (w. details), throw if to abort, else retry/ignore
335                       defaultReportSignatureError( res, str::Str() << userData.get<RpmDb::CheckPackageDetail>( "CheckPackageDetail" ) );
336                       break;
337                   }
338                 }
339               }
340             }
341           }
342         catch ( const UserRequestException & excpt )
343           {
344             ERR << "Failed to provide Package " << _package << endl;
345             if ( ! _retry )
346               ZYPP_RETHROW( excpt );
347           }
348         catch ( const FileCheckException & excpt )
349           {
350             ERR << "Failed to provide Package " << _package << endl;
351             if ( ! _retry )
352             {
353               const std::string & package_str = _package->asUserString();
354               // TranslatorExplanation %s = package being checked for integrity
355               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() ) ) )
356               {
357                 case repo::DownloadResolvableReport::RETRY:
358                   _retry = true;
359                   break;
360                 case repo::DownloadResolvableReport::IGNORE:
361                   ZYPP_THROW(SkipRequestException("User requested skip of corrupted file"));
362                   break;
363                 case repo::DownloadResolvableReport::ABORT:
364                   ZYPP_THROW(AbortRequestException("User requested to abort"));
365                   break;
366                 default:
367                   break;
368               }
369             }
370           }
371         catch ( const Exception & excpt )
372           {
373             ERR << "Failed to provide Package " << _package << endl;
374             if ( ! _retry )
375             {
376                 // Aything else gets reported
377                 const std::string & package_str = _package->asUserString();
378
379                 // TranslatorExplanation %s = name of the package being processed.
380                 std::string detail_str( str::form(_("Failed to provide Package %s. Do you want to retry retrieval?"), package_str.c_str() ) );
381                 detail_str += str::form( "\n\n%s", excpt.asUserHistory().c_str() );
382
383                 switch ( report()->problem( _package, repo::DownloadResolvableReport::IO, detail_str.c_str() ) )
384                 {
385                       case repo::DownloadResolvableReport::RETRY:
386                         _retry = true;
387                         break;
388                       case repo::DownloadResolvableReport::IGNORE:
389                         ZYPP_THROW(SkipRequestException("User requested skip of file", excpt));
390                         break;
391                       case repo::DownloadResolvableReport::ABORT:
392                         ZYPP_THROW(AbortRequestException("User requested to abort", excpt));
393                         break;
394                       default:
395                         ZYPP_RETHROW( excpt );
396                         break;
397                 }
398               }
399           }
400       } while ( _retry );
401
402       report()->finish( _package, repo::DownloadResolvableReport::NO_ERROR, std::string() );
403       MIL << "provided Package " << _package << " at " << ret << endl;
404       return ret;
405     }
406
407
408     ///////////////////////////////////////////////////////////////////
409     /// \class RpmPackageProvider
410     /// \brief RPM PackageProvider implementation.
411     ///////////////////////////////////////////////////////////////////
412     class RpmPackageProvider : public PackageProvider::Impl
413     {
414     public:
415       RpmPackageProvider( RepoMediaAccess & access_r,
416                           const Package::constPtr & package_r,
417                           const DeltaCandidates & deltas_r,
418                           const PackageProviderPolicy & policy_r )
419       : PackageProvider::Impl( access_r, package_r, deltas_r, policy_r )
420       {}
421
422     protected:
423       virtual ManagedFile doProvidePackageFromCache() const;
424
425       virtual ManagedFile doProvidePackage() const;
426
427     private:
428       typedef packagedelta::DeltaRpm    DeltaRpm;
429
430       ManagedFile tryDelta( const DeltaRpm & delta_r ) const;
431
432       bool progressDeltaDownload( int value ) const
433       { return report()->progressDeltaDownload( value ); }
434
435       void progressDeltaApply( int value ) const
436       { return report()->progressDeltaApply( value ); }
437
438       bool queryInstalled( const Edition & ed_r = Edition() ) const
439       { return _policy.queryInstalled( _package->name(), ed_r, _package->arch() ); }
440     };
441     ///////////////////////////////////////////////////////////////////
442
443     ManagedFile RpmPackageProvider::doProvidePackageFromCache() const
444     {
445       return ManagedFile( _package->cachedLocation() );
446     }
447
448     ManagedFile RpmPackageProvider::doProvidePackage() const
449     {
450       Url url;
451       RepoInfo info = _package->repoInfo();
452       // FIXME we only support the first url for now.
453       if ( info.baseUrlsEmpty() )
454         ZYPP_THROW(Exception("No url in repository."));
455       else
456         url = * info.baseUrlsBegin();
457
458       // check whether to process patch/delta rpms
459       if ( ZConfig::instance().download_use_deltarpm()
460         && ( url.schemeIsDownloading() || ZConfig::instance().download_use_deltarpm_always() ) )
461       {
462         std::list<DeltaRpm> deltaRpms;
463         _deltas.deltaRpms( _package ).swap( deltaRpms );
464
465         if ( ! deltaRpms.empty() && queryInstalled() && applydeltarpm::haveApplydeltarpm() )
466         {
467           for_( it, deltaRpms.begin(), deltaRpms.end())
468           {
469             DBG << "tryDelta " << *it << endl;
470             ManagedFile ret( tryDelta( *it ) );
471             if ( ! ret->empty() )
472               return ret;
473           }
474         }
475       }
476
477       // no patch/delta -> provide full package
478       return Base::doProvidePackage();
479     }
480
481     ManagedFile RpmPackageProvider::tryDelta( const DeltaRpm & delta_r ) const
482     {
483       if ( delta_r.baseversion().edition() != Edition::noedition
484            && ! queryInstalled( delta_r.baseversion().edition() ) )
485         return ManagedFile();
486
487       if ( ! applydeltarpm::quickcheck( delta_r.baseversion().sequenceinfo() ) )
488         return ManagedFile();
489
490       report()->startDeltaDownload( delta_r.location().filename(),
491                                     delta_r.location().downloadSize() );
492       ManagedFile delta;
493       try
494         {
495           ProvideFilePolicy policy;
496           policy.progressCB( bind( &RpmPackageProvider::progressDeltaDownload, this, _1 ) );
497           delta = _access.provideFile( delta_r.repository().info(), delta_r.location(), policy );
498         }
499       catch ( const Exception & excpt )
500         {
501           report()->problemDeltaDownload( excpt.asUserHistory() );
502           return ManagedFile();
503         }
504       report()->finishDeltaDownload();
505
506       report()->startDeltaApply( delta );
507       if ( ! applydeltarpm::check( delta_r.baseversion().sequenceinfo() ) )
508         {
509           report()->problemDeltaApply( _("applydeltarpm check failed.") );
510           return ManagedFile();
511         }
512
513       // build the package and put it into the cache
514       Pathname destination( _package->repoInfo().packagesPath() / _package->location().filename() );
515
516       if ( ! applydeltarpm::provide( delta, destination,
517                                      bind( &RpmPackageProvider::progressDeltaApply, this, _1 ) ) )
518         {
519           report()->problemDeltaApply( _("applydeltarpm failed.") );
520           return ManagedFile();
521         }
522       report()->finishDeltaApply();
523
524       return ManagedFile( destination, filesystem::unlink );
525     }
526
527 #if 0
528     ///////////////////////////////////////////////////////////////////
529     /// \class PluginPackageProvider
530     /// \brief Plugin PackageProvider implementation.
531     ///
532     /// Basically downloads the default package and calls a
533     /// 'stem'2rpm plugin to cteate the final .rpm package.
534     ///////////////////////////////////////////////////////////////////
535     class PluginPackageProvider : public PackageProvider::Impl
536     {
537     public:
538       PluginPackageProvider( const std::string & stem_r,
539                              RepoMediaAccess & access_r,
540                              const Package::constPtr & package_r,
541                              const DeltaCandidates & deltas_r,
542                              const PackageProviderPolicy & policy_r )
543       : Base( access_r, package_r, deltas_r, policy_r )
544       {}
545
546     protected:
547       virtual ManagedFile doProvidePackageFromCache() const
548       {
549         return Base::doProvidePackageFromCache();
550       }
551
552       virtual ManagedFile doProvidePackage() const
553       {
554         return Base::doProvidePackage();
555       }
556     };
557     ///////////////////////////////////////////////////////////////////
558 #endif
559
560     ///////////////////////////////////////////////////////////////////
561     //  class PackageProvider
562     ///////////////////////////////////////////////////////////////////
563
564     PackageProvider::Impl * PackageProvider::Impl::factoryMake( RepoMediaAccess & access_r,
565                                                                 const Package::constPtr & package_r,
566                                                                 const DeltaCandidates & deltas_r,
567                                                                 const PackageProviderPolicy & policy_r )
568     {
569       return new RpmPackageProvider( access_r, package_r, deltas_r, policy_r );
570     }
571
572     PackageProvider::PackageProvider( RepoMediaAccess & access_r,
573                                       const Package::constPtr & package_r,
574                                       const DeltaCandidates & deltas_r,
575                                       const PackageProviderPolicy & policy_r )
576     : _pimpl( Impl::factoryMake( access_r, package_r, deltas_r, policy_r ) )
577     {}
578
579     PackageProvider::~PackageProvider()
580     {}
581
582     ManagedFile PackageProvider::providePackage() const
583     { return _pimpl->providePackage(); }
584
585     ManagedFile PackageProvider::providePackageFromCache() const
586     { return _pimpl->providePackageFromCache(); }
587
588     bool PackageProvider::isCached() const
589     { return _pimpl->isCached(); }
590
591   } // namespace repo
592   ///////////////////////////////////////////////////////////////////
593 } // namespace zypp
594 ///////////////////////////////////////////////////////////////////