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