- more progress on fetcher capablity to use remote signed index.
[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 #include "zypp/parser/susetags/ContentFileReader.h"
26 #include "zypp/parser/susetags/RepoIndex.h"
27
28 using namespace std;
29
30 ///////////////////////////////////////////////////////////////////
31 namespace zypp
32 { /////////////////////////////////////////////////////////////////
33
34   /**
35    * class that represents indexes which add metadata
36    * to fetcher jobs and therefore need to be retrieved
37    * in advance.
38    */
39   struct FetcherIndex
40   {
41     FetcherIndex( const OnMediaLocation &loc )
42       : location(loc)
43     {
44     }
45       
46     OnMediaLocation location;
47   };
48   typedef shared_ptr<FetcherIndex> FetcherIndex_Ptr;
49       
50   /**
51    * Class to encapsulate the \ref OnMediaLocation object
52    * and the \ref FileChecker together
53    */
54   struct FetcherJob
55   {
56     FetcherJob( const OnMediaLocation &loc )
57       : location(loc)
58       , directory(false)
59       , recursive(false)
60     {
61       //MIL << location << endl;
62     }
63     
64     ~FetcherJob()
65     {
66       //MIL << location << " | * " << checkers.size() << endl;
67     }
68
69     OnMediaLocation location;
70     //CompositeFileChecker checkers;
71     list<FileChecker> checkers;
72     bool directory;
73     bool recursive;
74   };
75
76   typedef shared_ptr<FetcherJob> FetcherJob_Ptr;
77
78   std::ostream & operator<<( std::ostream & str, const FetcherJob_Ptr & obj )
79   {
80     return str << obj->location;
81   }
82
83
84   ///////////////////////////////////////////////////////////////////
85   //
86   //    CLASS NAME : Fetcher::Impl
87   //
88   /** Fetcher implementation. */
89   class Fetcher::Impl
90   {
91     friend std::ostream & operator<<( std::ostream & str, const Fetcher::Impl & obj );
92   public:
93       Impl();
94       
95     ~Impl() {
96       MIL << endl;
97      }
98       
99       void setOptions( Fetcher::Options options );
100       Fetcher::Options options() const;
101
102       void addIndex( const OnMediaLocation &resource );
103       void enqueue( const OnMediaLocation &resource, const FileChecker &checker = FileChecker()  );
104       void enqueueDir( const OnMediaLocation &resource, bool recursive, const FileChecker &checker = FileChecker() );
105       void enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker = FileChecker() );
106     void addCachePath( const Pathname &cache_dir );
107     void reset();
108     void start( const Pathname &dest_dir,
109                 MediaSetAccess &media,
110                 const ProgressData::ReceiverFnc & progress_receiver );
111
112     /** Offer default Impl. */
113     static shared_ptr<Impl> nullimpl()
114     {
115       static shared_ptr<Impl> _nullimpl( new Impl );
116       return _nullimpl;
117     }
118   private:
119       /**
120        * download the indexes and reads them
121        */
122       void readIndexes( MediaSetAccess &media, const Pathname &dest_dir);
123
124       /**
125        * reads a downloaded index file and updates internal
126        * attributes table
127        *
128        * The index lists files relative to a directory, which is
129        * normally the same as the index file is located.
130        */
131       void readIndex( const Pathname &index, const Pathname &basedir );
132
133       /** specific version of \ref readIndex for SHA1SUMS file */
134       void readSha1sumsIndex( const Pathname &index, const Pathname &basedir );
135       
136       /** specific version of \ref readIndex for SHA1SUMS file */
137       void readContentFileIndex( const Pathname &index, const Pathname &basedir );
138       
139       /**
140        * tries to provide the file represented by job into dest_dir by
141        * looking at the cache. If success, returns true, and the desired
142        * file should be available on dest_dir
143        */
144       bool provideFromCache( const OnMediaLocation &resource, const Pathname &dest_dir );
145       /**
146        * Validates the job against is checkers, by using the file instance
147        * on dest_dir
148        * \throws Exception
149        */
150       void validate( const OnMediaLocation &resource, const Pathname &dest_dir, const list<FileChecker> &checkers );
151
152       /**
153        * scan the directory and adds the individual jobs
154        */
155           void addDirJobs( MediaSetAccess &media, const OnMediaLocation &resource,
156                        const Pathname &dest_dir, bool recursive );
157       /**
158        * Provide the resource to \ref dest_dir
159        */
160       void provideToDest( MediaSetAccess &media, const OnMediaLocation &resource, const Pathname &dest_dir );
161
162   private:
163     friend Impl * rwcowClone<Impl>( const Impl * rhs );
164     /** clone for RWCOW_pointer */
165     Impl * clone() const
166     { return new Impl( *this ); }
167
168     list<FetcherJob_Ptr> _resources;
169     list<FetcherIndex_Ptr> _indexes;
170     list<Pathname> _caches;
171     // checksums read from the indexes
172     map<string, CheckSum> _checksums;
173     Fetcher::Options _options;      
174   };
175   ///////////////////////////////////////////////////////////////////
176
177   void Fetcher::Impl::enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker )
178   {
179     FetcherJob_Ptr job;
180     job.reset(new FetcherJob(resource));
181     ChecksumFileChecker digest_check(resource.checksum());
182     job->checkers.push_back(digest_check);
183     if ( checker )
184       job->checkers.push_back(checker);
185     _resources.push_back(job);
186   }
187    
188   Fetcher::Impl::Impl()
189       : _options(0)
190   {
191   }
192     
193   void Fetcher::Impl::setOptions( Fetcher::Options options )
194   { _options = options; }
195     
196   Fetcher::Options Fetcher::Impl::options() const
197   { return _options; }
198     
199   void Fetcher::Impl::enqueueDir( const OnMediaLocation &resource,
200                                   bool recursive,
201                                   const FileChecker &checker )
202   {
203     FetcherJob_Ptr job;
204     job.reset(new FetcherJob(resource));
205     if ( checker )
206         job->checkers.push_back(checker);
207     job->directory = true;
208     job->recursive = recursive;
209     _resources.push_back(job);
210   }
211
212   void Fetcher::Impl::enqueue( const OnMediaLocation &resource, const FileChecker &checker )
213   {
214     FetcherJob_Ptr job;
215     job.reset(new FetcherJob(resource));
216     if ( checker )
217       job->checkers.push_back(checker);
218     _resources.push_back(job);
219   }
220
221   void Fetcher::Impl::addIndex( const OnMediaLocation &resource )
222   {
223       MIL << "adding index " << resource << endl;
224       FetcherIndex_Ptr index;
225       index.reset(new FetcherIndex(resource));
226       _indexes.push_back(index);
227   }
228
229
230   void Fetcher::Impl::reset()
231   {
232     _resources.clear();
233     _indexes.clear();
234   }
235
236   void Fetcher::Impl::addCachePath( const Pathname &cache_dir )
237   {
238     PathInfo info(cache_dir);
239     if ( info.isExist() )
240     {
241       if ( info.isDir() )
242       {
243         DBG << "Adding fetcher cache: '" << cache_dir << "'." << endl;
244         _caches.push_back(cache_dir);
245       }
246       else
247       {
248         // don't add bad cache directory, just log the error
249         ERR << "Not adding cache: '" << cache_dir << "'. Not a directory." << endl;
250       }
251     }
252     else
253     {
254         ERR << "Not adding cache '" << cache_dir << "'. Path does not exists." << endl;
255     }
256
257   }
258
259   bool Fetcher::Impl::provideFromCache( const OnMediaLocation &resource, const Pathname &dest_dir )
260   {
261     Pathname dest_full_path = dest_dir + resource.filename();
262
263     // first check in the destination directory
264     if ( PathInfo(dest_full_path).isExist() )
265     {
266       if ( is_checksum( dest_full_path, resource.checksum() )
267            && (! resource.checksum().empty() ) )
268           return true;
269     }
270
271     MIL << "start fetcher with " << _caches.size() << " cache directories." << endl;
272     for_ ( it_cache, _caches.begin(), _caches.end() )
273     {
274       // does the current file exists in the current cache?
275       Pathname cached_file = *it_cache + resource.filename();
276       if ( PathInfo( cached_file ).isExist() )
277       {
278         DBG << "File '" << cached_file << "' exist, testing checksum " << resource.checksum() << endl;
279          // check the checksum
280         if ( is_checksum( cached_file, resource.checksum() ) && (! resource.checksum().empty() ) )
281         {
282           // cached
283           MIL << "file " << resource.filename() << " found in previous cache. Using cached copy." << endl;
284           // checksum is already checked.
285           // we could later implement double failover and try to download if file copy fails.
286            // replicate the complete path in the target directory
287           if( dest_full_path != cached_file )
288           {
289             if ( assert_dir( dest_full_path.dirname() ) != 0 )
290               ZYPP_THROW( Exception("Can't create " + dest_full_path.dirname().asString()));
291
292             if ( filesystem::hardlink(cached_file, dest_full_path ) != 0 )
293             {
294               WAR << "Can't hardlink '" << cached_file << "' to '" << dest_dir << "'. Trying copying." << endl;
295               if ( filesystem::copy(cached_file, dest_full_path ) != 0 )
296               {
297                 ERR << "Can't copy " << cached_file + " to " + dest_dir << endl;
298                 // try next cache
299                 continue;
300               }
301             }
302           }
303           // found in cache
304           return true;
305         }
306       }
307     } // iterate over caches
308     return false;
309   }
310     
311     void Fetcher::Impl::validate( const OnMediaLocation &resource, const Pathname &dest_dir, const list<FileChecker> &checkers )
312   {
313     // no matter where did we got the file, try to validate it:
314     Pathname localfile = dest_dir + resource.filename();
315     // call the checker function
316     try
317     {
318       MIL << "Checking job [" << localfile << "] (" << checkers.size() << " checkers )" << endl;
319
320       for ( list<FileChecker>::const_iterator it = checkers.begin();
321             it != checkers.end();
322             ++it )
323       {
324         if (*it)
325         {
326           (*it)(localfile);
327         }
328         else
329         {
330           ERR << "Invalid checker for '" << localfile << "'" << endl;
331         }
332       }
333
334     }
335     catch ( const FileCheckException &e )
336     {
337       ZYPP_RETHROW(e);
338     }
339     catch ( const Exception &e )
340     {
341       ZYPP_RETHROW(e);
342     }
343     catch (...)
344     {
345       ZYPP_THROW(Exception("Unknown error while validating " + resource.filename().asString()));
346     }
347   }
348
349   void Fetcher::Impl::addDirJobs( MediaSetAccess &media,
350                                   const OnMediaLocation &resource,
351                                   const Pathname &dest_dir, bool recursive  )
352   {
353       // first get the content of the directory so we can add
354       // individual transfer jobs
355       MIL << "Adding directory " << resource.filename() << endl;
356       filesystem::DirContent content;
357       media.dirInfo( content, resource.filename(), false /* dots */, resource.medianr());
358
359       // only try to add an index if it exists
360       filesystem::DirEntry shafile;
361       shafile.name = "SHA1SUMS"; shafile.type = filesystem::FT_FILE;
362       if ( find( content.begin(), content.end(), shafile ) != content.end() )
363       {              
364           // add the index of this directory
365           OnMediaLocation indexloc(resource);
366           indexloc.changeFilename(resource.filename() + "SHA1SUMS");
367           addIndex(indexloc);
368           // we need to read it now
369           readIndexes(media, dest_dir);
370       }
371
372       for ( filesystem::DirContent::const_iterator it = content.begin();
373             it != content.end();
374             ++it )
375       {
376           // skip SHA1SUMS* as they were already retrieved
377           if ( str::hasPrefix(it->name, "SHA1SUMS") )
378               continue;
379
380           Pathname filename = resource.filename() + it->name;
381
382           switch ( it->type )
383           {
384           case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
385           case filesystem::FT_FILE:
386           {
387               CheckSum chksm(resource.checksum());              
388               if ( _checksums.find(filename.asString()) != _checksums.end() )
389               {
390                   // the checksum can be replaced with the one in the index.
391                   chksm = _checksums[filename.asString()];
392                   //MIL << "resource " << filename << " has checksum in the index file." << endl;
393               }
394               else
395                   WAR << "Resource " << filename << " has no checksum in the index either." << endl;
396
397               enqueueDigested(OnMediaLocation(filename, resource.medianr()).setChecksum(chksm));
398               break;
399           }
400           case filesystem::FT_DIR: // newer directory.yast contain at least directory info
401               if ( recursive )
402                   addDirJobs(media, filename, dest_dir, recursive);
403               break;
404           default:
405               // don't provide devices, sockets, etc.
406               break;
407           }
408       }
409   }
410
411   void Fetcher::Impl::provideToDest( MediaSetAccess &media, const OnMediaLocation &resource, const Pathname &dest_dir )
412   {
413     bool got_from_cache = false;
414
415     // start look in cache
416     got_from_cache = provideFromCache(resource, dest_dir);
417
418     if ( ! got_from_cache )
419     {
420       MIL << "Not found in cache, downloading" << endl;
421
422       // try to get the file from the net
423       try
424       {
425         Pathname tmp_file = media.provideFile(resource);
426         Pathname dest_full_path = dest_dir + resource.filename();
427         if ( assert_dir( dest_full_path.dirname() ) != 0 )
428               ZYPP_THROW( Exception("Can't create " + dest_full_path.dirname().asString()));
429         if ( filesystem::copy(tmp_file, dest_full_path ) != 0 )
430         {
431           ZYPP_THROW( Exception("Can't copy " + tmp_file.asString() + " to " + dest_dir.asString()));
432         }
433
434         media.releaseFile(resource); //not needed anymore, only eat space
435       }
436       catch (Exception & excpt_r)
437       {
438         ZYPP_CAUGHT(excpt_r);
439         excpt_r.remember("Can't provide " + resource.filename().asString() + " : " + excpt_r.msg());
440         ZYPP_RETHROW(excpt_r);
441       }
442     }
443     else
444     {
445       // We got the file from cache
446       // continue with next file
447         return;
448     }
449   }
450
451   // helper class to consume a content file
452   struct ContentReaderHelper : public parser::susetags::ContentFileReader
453   {
454     ContentReaderHelper()
455     {
456       setRepoIndexConsumer( bind( &ContentReaderHelper::consumeIndex, this, _1 ) );
457     }
458         
459     void consumeIndex( const parser::susetags::RepoIndex_Ptr & data_r )
460     { _repoindex = data_r; }
461
462     parser::susetags::RepoIndex_Ptr _repoindex;
463   };
464
465   // generic function for reading indexes
466   void Fetcher::Impl::readIndex( const Pathname &index, const Pathname &basedir )
467   {
468     if ( index.basename() == "SHA1SUMS" )
469       readSha1sumsIndex(index, basedir);
470     else if ( index.basename() == "content" )
471       readContentFileIndex(index, basedir);
472     else
473       WAR << index << ": index file format not known" << endl;
474   }
475
476   // reads a content file index
477   void Fetcher::Impl::readContentFileIndex( const Pathname &index, const Pathname &basedir )
478   {
479       ContentReaderHelper reader;
480       reader.parse(index);
481       MIL << index << " contains " << reader._repoindex->mediaFileChecksums.size() << " checksums." << endl;
482       for_( it, reader._repoindex->mediaFileChecksums.begin(), reader._repoindex->mediaFileChecksums.end() )
483       {
484           // content file entries don't start with /
485           _checksums[(basedir + it->first).asString()] = it->second;
486       }
487   }
488
489   // reads a SHA1SUMS file index
490   void Fetcher::Impl::readSha1sumsIndex( const Pathname &index, const Pathname &basedir )
491   {
492       std::ifstream in( index.c_str() );
493       string buffer;
494       if ( ! in.fail() )
495       {
496           while ( getline(in, buffer) )
497           {
498               vector<string> words;
499               str::split( buffer, back_inserter(words) );
500               if ( words.size() != 2 )
501                   ZYPP_THROW(Exception("Wrong format for SHA1SUMS file"));
502               //MIL << "check: '" << words[0] << "' | '" << words[1] << "'" << endl;
503               if ( ! words[1].empty() )
504                   _checksums[(basedir + words[1]).asString()] = CheckSum::sha1(words[0]);
505           }
506       }
507       else
508           ZYPP_THROW(Exception("Can't open SHA1SUMS file: " + index.asString()));
509   }
510     
511   void Fetcher::Impl::readIndexes( MediaSetAccess &media, const Pathname &dest_dir)
512   {
513       // if there is no indexes, then just return to avoid
514       // the directory listing
515       if ( _indexes.empty() )
516       {
517           MIL << "No indexes to read." << endl;
518           return;
519       }
520
521       // create a new fetcher with a different state to transfer the
522       // file containing checksums and its signature
523       Fetcher fetcher;
524       // signature checker for index. We havent got the signature from
525       // the nextwork yet.
526       SignatureFileChecker sigchecker;
527       
528       for ( list<FetcherIndex_Ptr>::const_iterator it_idx = _indexes.begin();
529             it_idx != _indexes.end(); ++it_idx )
530       {
531           MIL << "reading index " << (*it_idx)->location << endl;
532           // build the name of the index and the signature
533           OnMediaLocation idxloc((*it_idx)->location);
534           OnMediaLocation sigloc((*it_idx)->location.setOptional(true));
535           OnMediaLocation keyloc((*it_idx)->location.setOptional(true));
536
537           // calculate signature and key name
538           sigloc.changeFilename( sigloc.filename().extend(".asc") );
539           keyloc.changeFilename( keyloc.filename().extend(".key") );
540
541           //assert_dir(dest_dir + idxloc.filename().dirname());
542
543           // transfer the signature
544           fetcher.enqueue(sigloc);
545           fetcher.start( dest_dir, media );
546           // if we get the signature, update the checker
547           sigchecker = SignatureFileChecker(dest_dir + sigloc.filename());
548           fetcher.reset();
549           
550           // now the key
551           fetcher.enqueue(keyloc);
552           fetcher.start( dest_dir, media );
553           fetcher.reset();
554
555           // now the index itself
556           fetcher.enqueue( idxloc, FileChecker(sigchecker) );
557           fetcher.start( dest_dir, media );
558           fetcher.reset();
559
560           // now we have the indexes in dest_dir
561           readIndex( dest_dir + idxloc.filename(), idxloc.filename().dirname() );
562       }
563       MIL << "done reading indexes" << endl;
564   }
565     
566   void Fetcher::Impl::start( const Pathname &dest_dir,
567                              MediaSetAccess &media,
568                              const ProgressData::ReceiverFnc & progress_receiver )
569   {
570     ProgressData progress(_resources.size());
571     progress.sendTo(progress_receiver);
572
573     readIndexes(media, dest_dir);
574
575     for ( list<FetcherJob_Ptr>::const_iterator it_res = _resources.begin(); it_res != _resources.end(); ++it_res )
576     {
577
578       if ( (*it_res)->directory )
579       {
580           const OnMediaLocation location((*it_res)->location);
581           addDirJobs(media, location, dest_dir, true);
582           continue;
583       }
584
585       provideToDest(media, (*it_res)->location, dest_dir);
586
587       // if the checksum is empty, but the checksum is in one of the
588       // indexes checksum, then add a checker
589       if ( (*it_res)->location.checksum().empty() )
590       {
591           if ( _checksums.find((*it_res)->location.filename().asString()) 
592                != _checksums.end() )
593           {
594               // the checksum can be replaced with the one in the index.
595               CheckSum chksm = _checksums[(*it_res)->location.filename().asString()];
596               ChecksumFileChecker digest_check(chksm);    
597               (*it_res)->checkers.push_back(digest_check);
598           }
599       }
600       
601       // validate job, this throws if not valid
602       validate((*it_res)->location, dest_dir, (*it_res)->checkers);
603
604       if ( ! progress.incr() )
605         ZYPP_THROW(AbortRequestException());
606     } // for each job
607   }
608
609   /** \relates Fetcher::Impl Stream output */
610   inline std::ostream & operator<<( std::ostream & str, const Fetcher::Impl & obj )
611   {
612       for ( list<FetcherJob_Ptr>::const_iterator it_res = obj._resources.begin(); it_res != obj._resources.end(); ++it_res )
613       {
614           str << *it_res;
615       }
616       return str;
617   }
618
619   ///////////////////////////////////////////////////////////////////
620   //
621   //    CLASS NAME : Fetcher
622   //
623   ///////////////////////////////////////////////////////////////////
624
625   ///////////////////////////////////////////////////////////////////
626   //
627   //    METHOD NAME : Fetcher::Fetcher
628   //    METHOD TYPE : Ctor
629   //
630   Fetcher::Fetcher()
631   : _pimpl( new Impl() )
632   {}
633
634   ///////////////////////////////////////////////////////////////////
635   //
636   //    METHOD NAME : Fetcher::~Fetcher
637   //    METHOD TYPE : Dtor
638   //
639   Fetcher::~Fetcher()
640   {}
641
642   void Fetcher::setOptions( Fetcher::Options options )
643   {
644     _pimpl->setOptions(options);
645   }
646     
647   Fetcher::Options Fetcher::options() const
648   {
649     return _pimpl->options();
650   }
651
652   void Fetcher::enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker )
653   {
654     _pimpl->enqueueDigested(resource, checker);
655   }
656
657   void Fetcher::enqueueDir( const OnMediaLocation &resource,
658                             bool recursive,
659                             const FileChecker &checker )
660   {
661       _pimpl->enqueueDir(resource, recursive, checker);
662   }
663
664   void Fetcher::addIndex( const OnMediaLocation &resource )
665   {
666     _pimpl->addIndex(resource);
667   }
668
669
670   void Fetcher::enqueue( const OnMediaLocation &resource, const FileChecker &checker  )
671   {
672     _pimpl->enqueue(resource, checker);
673   }
674
675   void Fetcher::addCachePath( const Pathname &cache_dir )
676   {
677     _pimpl->addCachePath(cache_dir);
678   }
679
680   void Fetcher::reset()
681   {
682     _pimpl->reset();
683   }
684
685   void Fetcher::start( const Pathname &dest_dir,
686                        MediaSetAccess &media,
687                        const ProgressData::ReceiverFnc & progress_receiver )
688   {
689     _pimpl->start(dest_dir, media, progress_receiver);
690   }
691
692
693   /******************************************************************
694   **
695   **    FUNCTION NAME : operator<<
696   **    FUNCTION TYPE : std::ostream &
697   */
698   std::ostream & operator<<( std::ostream & str, const Fetcher & obj )
699   {
700     return str << *obj._pimpl;
701   }
702
703   /////////////////////////////////////////////////////////////////
704 } // namespace zypp
705 ///////////////////////////////////////////////////////////////////
706