seems to work, or better called, first working version, now checks the checksums too
[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()
403                               .setFilename(filename)
404                               .setChecksum(checksum));
405                   
406               break;
407           }
408           case filesystem::FT_DIR: // newer directory.yast contain at least directory info
409               if ( recursive )
410                   addDirJobs(media, filename, dest_dir, recursive);
411               break;
412           default:
413               // don't provide devices, sockets, etc.
414               break;
415           }
416       }
417   }
418
419   void Fetcher::Impl::provideToDest( MediaSetAccess &media, const OnMediaLocation &resource, const Pathname &dest_dir )
420   {
421     bool got_from_cache = false;
422       
423     // start look in cache
424     got_from_cache = provideFromCache(resource, dest_dir);
425       
426     if ( ! got_from_cache )
427     {
428       MIL << "Not found in cache, downloading" << endl;
429         
430       // try to get the file from the net
431       try
432       {
433         Pathname tmp_file = media.provideFile(resource);
434         Pathname dest_full_path = dest_dir + resource.filename();
435         if ( assert_dir( dest_full_path.dirname() ) != 0 )
436               ZYPP_THROW( Exception("Can't create " + dest_full_path.dirname().asString()));
437         if ( filesystem::copy(tmp_file, dest_full_path ) != 0 )
438         {
439           ZYPP_THROW( Exception("Can't copy " + tmp_file.asString() + " to " + dest_dir.asString()));
440         }
441
442         media.releaseFile(resource); //not needed anymore, only eat space
443       }
444       catch (Exception & excpt_r)
445       {
446         ZYPP_CAUGHT(excpt_r);
447         excpt_r.remember("Can't provide " + resource.filename().asString() + " : " + excpt_r.msg());
448         ZYPP_RETHROW(excpt_r);
449       }
450     }
451     else
452     {
453       // We got the file from cache
454       // continue with next file
455         return;
456     }
457   }  
458
459   void Fetcher::Impl::start( const Pathname &dest_dir,
460                              MediaSetAccess &media,
461                              const ProgressData::ReceiverFnc & progress_receiver )
462   {
463     ProgressData progress(_resources.size());
464     progress.sendTo(progress_receiver);
465
466     for ( list<FetcherJob_Ptr>::const_iterator it_res = _resources.begin(); it_res != _resources.end(); ++it_res )
467     { 
468
469       if ( (*it_res)->directory )
470       {
471           const OnMediaLocation location((*it_res)->location);
472           addDirJobs(media, location, dest_dir, true);
473           continue;
474       }
475
476       provideToDest(media, (*it_res)->location, dest_dir);
477
478       // validate job, this throws if not valid
479       validate((*it_res)->location, dest_dir, (*it_res)->checkers);
480       
481       if ( ! progress.incr() )
482         ZYPP_THROW(AbortRequestException());
483     } // for each job
484   }
485
486   /** \relates Fetcher::Impl Stream output */
487   inline std::ostream & operator<<( std::ostream & str, const Fetcher::Impl & obj )
488   {
489       for ( list<FetcherJob_Ptr>::const_iterator it_res = obj._resources.begin(); it_res != obj._resources.end(); ++it_res )
490       { 
491           str << *it_res;
492       }
493       return str;
494   }
495
496   ///////////////////////////////////////////////////////////////////
497   //
498   //    CLASS NAME : Fetcher
499   //
500   ///////////////////////////////////////////////////////////////////
501
502   ///////////////////////////////////////////////////////////////////
503   //
504   //    METHOD NAME : Fetcher::Fetcher
505   //    METHOD TYPE : Ctor
506   //
507   Fetcher::Fetcher()
508   : _pimpl( new Impl() )
509   {}
510
511   ///////////////////////////////////////////////////////////////////
512   //
513   //    METHOD NAME : Fetcher::~Fetcher
514   //    METHOD TYPE : Dtor
515   //
516   Fetcher::~Fetcher()
517   {}
518
519   void Fetcher::enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker )
520   {
521     _pimpl->enqueueDigested(resource, checker);
522   }
523
524   void Fetcher::enqueueDir( const OnMediaLocation &resource,
525                             bool recursive,
526                             const FileChecker &checker )
527   {
528       _pimpl->enqueueDir(resource, recursive, checker);
529   }
530
531   void Fetcher::enqueue( const OnMediaLocation &resource, const FileChecker &checker  )
532   {
533     _pimpl->enqueue(resource, checker);
534   }
535
536   void Fetcher::addCachePath( const Pathname &cache_dir )
537   {
538     _pimpl->addCachePath(cache_dir);
539   }
540
541   void Fetcher::reset()
542   {
543     _pimpl->reset();
544   }
545
546   void Fetcher::start( const Pathname &dest_dir,
547                        MediaSetAccess &media,
548                        const ProgressData::ReceiverFnc & progress_receiver )
549   {
550     _pimpl->start(dest_dir, media, progress_receiver);
551   }
552
553
554   /******************************************************************
555   **
556   **    FUNCTION NAME : operator<<
557   **    FUNCTION TYPE : std::ostream &
558   */
559   std::ostream & operator<<( std::ostream & str, const Fetcher & obj )
560   {
561     return str << *obj._pimpl;
562   }
563
564   /////////////////////////////////////////////////////////////////
565 } // namespace zypp
566 ///////////////////////////////////////////////////////////////////
567