Remove error prone methods from OnMediaLocation API to prevent
[platform/upstream/libzypp.git] / zypp / Fetcher.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/Fetcher.cc
10  *
11 */
12 #include <iostream>
13 #include <fstream>
14 #include <list>
15 #include <map>
16
17 #include "zypp/base/Easy.h"
18 #include "zypp/base/Logger.h"
19 #include "zypp/base/PtrTypes.h"
20 #include "zypp/base/DefaultIntegral.h"
21 #include "zypp/base/String.h"
22 #include "zypp/Fetcher.h"
23 #include "zypp/CheckSum.h"
24 #include "zypp/base/UserRequestException.h"
25
26 using namespace std;
27
28 ///////////////////////////////////////////////////////////////////
29 namespace zypp
30 { /////////////////////////////////////////////////////////////////
31
32   /**
33    * Class to encapsulate the \ref OnMediaLocation object
34    * and the \ref FileChecker together
35    */
36   struct FetcherJob
37   {
38
39     FetcherJob( const OnMediaLocation &loc )
40       : location(loc)
41       , directory(false)
42       , recursive(false)
43     {
44       //MIL << location << endl;
45     }
46
47     ~FetcherJob()
48     {
49       //MIL << location << " | * " << checkers.size() << endl;
50     }
51
52     OnMediaLocation location;
53     //CompositeFileChecker checkers;
54     list<FileChecker> checkers;
55     bool directory;
56     bool recursive;
57   };
58
59   typedef shared_ptr<FetcherJob> FetcherJob_Ptr;
60
61   std::ostream & operator<<( std::ostream & str, const FetcherJob_Ptr & obj )
62   {
63     return str << obj->location;
64   }
65
66
67   ///////////////////////////////////////////////////////////////////
68   //
69   //    CLASS NAME : Fetcher::Impl
70   //
71   /** Fetcher implementation. */
72   class Fetcher::Impl
73   {
74     friend std::ostream & operator<<( std::ostream & str, const Fetcher::Impl & obj );
75   public:
76     Impl() {}
77     ~Impl() {
78       MIL << endl;
79      }
80
81       void enqueue( const OnMediaLocation &resource, const FileChecker &checker = FileChecker()  );
82       void enqueueDir( const OnMediaLocation &resource, bool recursive, const FileChecker &checker = FileChecker() );
83       void enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker = FileChecker() );
84     void addCachePath( const Pathname &cache_dir );
85     void reset();
86     void start( const Pathname &dest_dir,
87                 MediaSetAccess &media,
88                 const ProgressData::ReceiverFnc & progress_receiver );
89
90     /** Offer default Impl. */
91     static shared_ptr<Impl> nullimpl()
92     {
93       static shared_ptr<Impl> _nullimpl( new Impl );
94       return _nullimpl;
95     }
96   private:
97       /**
98        * tries to provide the file represented by job into dest_dir by
99        * looking at the cache. If success, returns true, and the desired
100        * file should be available on dest_dir
101        */
102       bool provideFromCache( const OnMediaLocation &resource, const Pathname &dest_dir );
103       /**
104        * Validates the job against is checkers, by using the file instance
105        * on dest_dir
106        * \throws Exception
107        */
108       void validate( const OnMediaLocation &resource, const Pathname &dest_dir, const list<FileChecker> &checkers );
109
110       /**
111        * scan the directory and adds the individual jobs
112        */
113           void addDirJobs( MediaSetAccess &media, const OnMediaLocation &resource,
114                        const Pathname &dest_dir, bool recursive );
115       /**
116        * Provide the resource to \ref dest_dir
117        */
118       void provideToDest( MediaSetAccess &media, const OnMediaLocation &resource, const Pathname &dest_dir );
119
120   private:
121     friend Impl * rwcowClone<Impl>( const Impl * rhs );
122     /** clone for RWCOW_pointer */
123     Impl * clone() const
124     { return new Impl( *this ); }
125
126     std::list<FetcherJob_Ptr> _resources;
127     std::list<Pathname> _caches;
128   };
129   ///////////////////////////////////////////////////////////////////
130
131   void Fetcher::Impl::enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker )
132   {
133     FetcherJob_Ptr job;
134     job.reset(new FetcherJob(resource));
135     ChecksumFileChecker digest_check(resource.checksum());
136     job->checkers.push_back(digest_check);
137     if ( checker )
138       job->checkers.push_back(checker);
139     _resources.push_back(job);
140   }
141
142   void Fetcher::Impl::enqueueDir( const OnMediaLocation &resource,
143                                   bool recursive,
144                                   const FileChecker &checker )
145   {
146     FetcherJob_Ptr job;
147     job.reset(new FetcherJob(resource));
148     if ( checker )
149         job->checkers.push_back(checker);
150     job->directory = true;
151     job->recursive = recursive;
152     _resources.push_back(job);
153   }
154
155   void Fetcher::Impl::enqueue( const OnMediaLocation &resource, const FileChecker &checker )
156   {
157     FetcherJob_Ptr job;
158     job.reset(new FetcherJob(resource));
159     if ( checker )
160       job->checkers.push_back(checker);
161     _resources.push_back(job);
162   }
163
164   void Fetcher::Impl::reset()
165   {
166     _resources.clear();
167   }
168
169   void Fetcher::Impl::addCachePath( const Pathname &cache_dir )
170   {
171     PathInfo info(cache_dir);
172     if ( info.isExist() )
173     {
174       if ( info.isDir() )
175       {
176         DBG << "Adding fetcher cache: '" << cache_dir << "'." << endl;
177         _caches.push_back(cache_dir);
178       }
179       else
180       {
181         // don't add bad cache directory, just log the error
182         ERR << "Not adding cache: '" << cache_dir << "'. Not a directory." << endl;
183       }
184     }
185     else
186     {
187         ERR << "Not adding cache '" << cache_dir << "'. Path does not exists." << endl;
188     }
189
190   }
191
192   bool Fetcher::Impl::provideFromCache( const OnMediaLocation &resource, const Pathname &dest_dir )
193   {
194     Pathname dest_full_path = dest_dir + resource.filename();
195
196     // first check in the destination directory
197     if ( PathInfo(dest_full_path).isExist() )
198     {
199       if ( is_checksum( dest_full_path, resource.checksum() )
200            && (! resource.checksum().empty() ) )
201           return true;
202     }
203
204     MIL << "start fetcher with " << _caches.size() << " cache directories." << endl;
205     for_ ( it_cache, _caches.begin(), _caches.end() )
206     {
207       // does the current file exists in the current cache?
208       Pathname cached_file = *it_cache + resource.filename();
209       if ( PathInfo( cached_file ).isExist() )
210       {
211         DBG << "File '" << cached_file << "' exist, testing checksum " << resource.checksum() << endl;
212          // check the checksum
213         if ( is_checksum( cached_file, resource.checksum() ) && (! resource.checksum().empty() ) )
214         {
215           // cached
216           MIL << "file " << resource.filename() << " found in previous cache. Using cached copy." << endl;
217           // checksum is already checked.
218           // we could later implement double failover and try to download if file copy fails.
219            // replicate the complete path in the target directory
220           if( dest_full_path != cached_file )
221           {
222             if ( assert_dir( dest_full_path.dirname() ) != 0 )
223               ZYPP_THROW( Exception("Can't create " + dest_full_path.dirname().asString()));
224
225             if ( filesystem::hardlink(cached_file, dest_full_path ) != 0 )
226             {
227               WAR << "Can't hardlink '" << cached_file << "' to '" << dest_dir << "'. Trying copying." << endl;
228               if ( filesystem::copy(cached_file, dest_full_path ) != 0 )
229               {
230                 ERR << "Can't copy " << cached_file + " to " + dest_dir << endl;
231                 // try next cache
232                 continue;
233               }
234             }
235           }
236           // found in cache
237           return true;
238         }
239       }
240     } // iterate over caches
241     return false;
242   }
243
244     void Fetcher::Impl::validate( const OnMediaLocation &resource, const Pathname &dest_dir, const list<FileChecker> &checkers )
245   {
246     // no matter where did we got the file, try to validate it:
247     Pathname localfile = dest_dir + resource.filename();
248     // call the checker function
249     try {
250       MIL << "Checking job [" << localfile << "] (" << checkers.size() << " checkers )" << endl;
251       for ( list<FileChecker>::const_iterator it = checkers.begin();
252             it != checkers.end();
253             ++it )
254       {
255         if (*it)
256         {
257           (*it)(localfile);
258         }
259         else
260         {
261           ERR << "Invalid checker for '" << localfile << "'" << endl;
262         }
263       }
264
265     }
266     catch ( const FileCheckException &e )
267     {
268       ZYPP_RETHROW(e);
269     }
270     catch ( const Exception &e )
271     {
272       ZYPP_RETHROW(e);
273     }
274     catch (...)
275     {
276       ZYPP_THROW(Exception("Unknown error while validating " + resource.filename().asString()));
277     }
278   }
279
280   void Fetcher::Impl::addDirJobs( MediaSetAccess &media,
281                                   const OnMediaLocation &resource,
282                                   const Pathname &dest_dir, bool recursive  )
283   {
284       // first get the content of the directory so we can add
285       // individual transfer jobs
286       MIL << "Adding directory " << resource.filename() << endl;
287       filesystem::DirContent content;
288       media.dirInfo( content, resource.filename(), false /* dots */, resource.medianr());
289
290       filesystem::DirEntry shafile, shasig, shakey;
291       shafile.name = "SHA1SUMS"; shafile.type = filesystem::FT_FILE;
292       shasig.name = "SHA1SUMS.asc"; shasig.type = filesystem::FT_FILE;
293       shakey.name = "SHA1SUMS.key"; shakey.type = filesystem::FT_FILE;
294
295       // create a new fetcher with a different state to transfer the
296       // file containing checksums and its signature
297       Fetcher fetcher;
298       // signature checker for SHA1SUMS. We havent got the signature from
299       // the nextwork yet.
300       SignatureFileChecker sigchecker;
301
302       // now try to find the SHA1SUMS signature
303       if ( find(content.begin(), content.end(), shasig)
304            != content.end() )
305       {
306           MIL << "found checksums signature file: " << shasig.name << endl;
307           // TODO refactor with OnMediaLocation::extend or something
308           fetcher.enqueue( OnMediaLocation(resource.filename() + shasig.name, resource.medianr()).setOptional(true) );
309           assert_dir(dest_dir + resource.filename());
310           fetcher.start( dest_dir, media );
311
312           // if we get the signature, update the checker
313           sigchecker = SignatureFileChecker(dest_dir + resource.filename() + shasig.name);
314           fetcher.reset();
315       }
316       else
317           MIL << "no signature for " << shafile.name << endl;
318
319       // look for the SHA1SUMS.key file
320       if ( find(content.begin(), content.end(), shakey)
321            != content.end() )
322       {
323           MIL << "found public key file: " << shakey.name << endl;
324           fetcher.enqueue( OnMediaLocation(resource.filename() + shakey.name, resource.medianr()).setOptional(true) );
325           assert_dir(dest_dir + resource.filename());
326           fetcher.start( dest_dir, media );
327           fetcher.reset();
328           KeyContext context;
329           sigchecker.addPublicKey(dest_dir + resource.filename() + shakey.name, context);
330       }
331
332       // look for the SHA1SUMS public key file
333       if ( find(content.begin(), content.end(), shafile)
334            != content.end() )
335       {
336           MIL << "found checksums file: " << shafile.name << endl;
337           fetcher.enqueue( OnMediaLocation(resource.filename() + shafile.name, resource.medianr()).setOptional(true) );
338           assert_dir(dest_dir + resource.filename());
339           fetcher.start( dest_dir, media );
340           fetcher.reset();
341       }
342
343       // hash table to store checksums
344       map<string, CheckSum> checksums;
345
346       // look for the SHA1SUMS file
347       if ( find(content.begin(), content.end(), shafile) != content.end() )
348       {
349           MIL << "found checksums file: " << shafile.name << endl;
350           fetcher.enqueue( OnMediaLocation(
351                                resource.filename() + shafile.name,
352                                resource.medianr()).setOptional(true),
353                            FileChecker(sigchecker) );
354           assert_dir(dest_dir + resource.filename());
355           fetcher.start( dest_dir + resource.filename(), media );
356           fetcher.reset();
357
358           // now read the SHA1SUMS file
359           Pathname pShafile = dest_dir + resource.filename() + shafile.name;
360           std::ifstream in( pShafile.c_str() );
361           string buffer;
362           if ( ! in.fail() )
363           {
364               while ( getline(in, buffer) )
365               {
366                   vector<string> words;
367                   str::split( buffer, back_inserter(words) );
368                   if ( words.size() != 2 )
369                       ZYPP_THROW(Exception("Wrong format for SHA1SUMS file"));
370                   //MIL << "check: '" << words[0] << "' | '" << words[1] << "'" << endl;
371                   if ( ! words[1].empty() )
372                       checksums[words[1]] = CheckSum::sha1(words[0]);
373               }
374           }
375           else
376               ZYPP_THROW(Exception("Can't open SHA1SUMS file: " + pShafile.asString()));
377       }
378
379       for ( filesystem::DirContent::const_iterator it = content.begin();
380             it != content.end();
381             ++it )
382       {
383           // skip SHA1SUMS* as they were already retrieved
384           if ( str::hasPrefix(it->name, "SHA1SUMS") )
385               continue;
386
387           Pathname filename = resource.filename() + it->name;
388
389           switch ( it->type )
390           {
391           case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
392           case filesystem::FT_FILE:
393           {
394               CheckSum checksum;
395               if ( checksums.find(it->name) != checksums.end() )
396                   checksum = checksums[it->name];
397
398               // create a resource from the file
399               // if checksum was not available we will have a empty
400               // checksum, which will end in a validation anyway
401               // warning the user that there is no checksum
402               enqueueDigested(OnMediaLocation(filename, resource.medianr())
403                               .setChecksum(checksum));
404
405               break;
406           }
407           case filesystem::FT_DIR: // newer directory.yast contain at least directory info
408               if ( recursive )
409                   addDirJobs(media, filename, dest_dir, recursive);
410               break;
411           default:
412               // don't provide devices, sockets, etc.
413               break;
414           }
415       }
416   }
417
418   void Fetcher::Impl::provideToDest( MediaSetAccess &media, const OnMediaLocation &resource, const Pathname &dest_dir )
419   {
420     bool got_from_cache = false;
421
422     // start look in cache
423     got_from_cache = provideFromCache(resource, dest_dir);
424
425     if ( ! got_from_cache )
426     {
427       MIL << "Not found in cache, downloading" << endl;
428
429       // try to get the file from the net
430       try
431       {
432         Pathname tmp_file = media.provideFile(resource);
433         Pathname dest_full_path = dest_dir + resource.filename();
434         if ( assert_dir( dest_full_path.dirname() ) != 0 )
435               ZYPP_THROW( Exception("Can't create " + dest_full_path.dirname().asString()));
436         if ( filesystem::copy(tmp_file, dest_full_path ) != 0 )
437         {
438           ZYPP_THROW( Exception("Can't copy " + tmp_file.asString() + " to " + dest_dir.asString()));
439         }
440
441         media.releaseFile(resource); //not needed anymore, only eat space
442       }
443       catch (Exception & excpt_r)
444       {
445         ZYPP_CAUGHT(excpt_r);
446         excpt_r.remember("Can't provide " + resource.filename().asString() + " : " + excpt_r.msg());
447         ZYPP_RETHROW(excpt_r);
448       }
449     }
450     else
451     {
452       // We got the file from cache
453       // continue with next file
454         return;
455     }
456   }
457
458   void Fetcher::Impl::start( const Pathname &dest_dir,
459                              MediaSetAccess &media,
460                              const ProgressData::ReceiverFnc & progress_receiver )
461   {
462     ProgressData progress(_resources.size());
463     progress.sendTo(progress_receiver);
464
465     for ( list<FetcherJob_Ptr>::const_iterator it_res = _resources.begin(); it_res != _resources.end(); ++it_res )
466     {
467
468       if ( (*it_res)->directory )
469       {
470           const OnMediaLocation location((*it_res)->location);
471           addDirJobs(media, location, dest_dir, true);
472           continue;
473       }
474
475       provideToDest(media, (*it_res)->location, dest_dir);
476
477       // validate job, this throws if not valid
478       validate((*it_res)->location, dest_dir, (*it_res)->checkers);
479
480       if ( ! progress.incr() )
481         ZYPP_THROW(AbortRequestException());
482     } // for each job
483   }
484
485   /** \relates Fetcher::Impl Stream output */
486   inline std::ostream & operator<<( std::ostream & str, const Fetcher::Impl & obj )
487   {
488       for ( list<FetcherJob_Ptr>::const_iterator it_res = obj._resources.begin(); it_res != obj._resources.end(); ++it_res )
489       {
490           str << *it_res;
491       }
492       return str;
493   }
494
495   ///////////////////////////////////////////////////////////////////
496   //
497   //    CLASS NAME : Fetcher
498   //
499   ///////////////////////////////////////////////////////////////////
500
501   ///////////////////////////////////////////////////////////////////
502   //
503   //    METHOD NAME : Fetcher::Fetcher
504   //    METHOD TYPE : Ctor
505   //
506   Fetcher::Fetcher()
507   : _pimpl( new Impl() )
508   {}
509
510   ///////////////////////////////////////////////////////////////////
511   //
512   //    METHOD NAME : Fetcher::~Fetcher
513   //    METHOD TYPE : Dtor
514   //
515   Fetcher::~Fetcher()
516   {}
517
518   void Fetcher::enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker )
519   {
520     _pimpl->enqueueDigested(resource, checker);
521   }
522
523   void Fetcher::enqueueDir( const OnMediaLocation &resource,
524                             bool recursive,
525                             const FileChecker &checker )
526   {
527       _pimpl->enqueueDir(resource, recursive, checker);
528   }
529
530   void Fetcher::enqueue( const OnMediaLocation &resource, const FileChecker &checker  )
531   {
532     _pimpl->enqueue(resource, checker);
533   }
534
535   void Fetcher::addCachePath( const Pathname &cache_dir )
536   {
537     _pimpl->addCachePath(cache_dir);
538   }
539
540   void Fetcher::reset()
541   {
542     _pimpl->reset();
543   }
544
545   void Fetcher::start( const Pathname &dest_dir,
546                        MediaSetAccess &media,
547                        const ProgressData::ReceiverFnc & progress_receiver )
548   {
549     _pimpl->start(dest_dir, media, progress_receiver);
550   }
551
552
553   /******************************************************************
554   **
555   **    FUNCTION NAME : operator<<
556   **    FUNCTION TYPE : std::ostream &
557   */
558   std::ostream & operator<<( std::ostream & str, const Fetcher & obj )
559   {
560     return str << *obj._pimpl;
561   }
562
563   /////////////////////////////////////////////////////////////////
564 } // namespace zypp
565 ///////////////////////////////////////////////////////////////////
566