- fix typo in error message (Ralf)
[apt.git] / apt-pkg / acquire-item.cc
1 // -*- mode: c++; mode: fold -*-
2 // Description                                                          /*{{{*/
3 // $Id: acquire-item.cc,v 1.46 2003/02/02 22:19:17 jgg Exp $
4 /* ######################################################################
5
6    Acquire Item - Item to acquire
7
8    Each item can download to exactly one file at a time. This means you
9    cannot create an item that fetches two uri's to two files at the same 
10    time. The pkgAcqIndex class creates a second class upon instantiation
11    to fetch the other index files because of this.
12
13    ##################################################################### */
14                                                                         /*}}}*/
15 // Include Files                                                        /*{{{*/
16 #ifdef __GNUG__
17 #pragma implementation "apt-pkg/acquire-item.h"
18 #endif
19 #include <apt-pkg/acquire-item.h>
20 #include <apt-pkg/configuration.h>
21 #include <apt-pkg/sourcelist.h>
22 #include <apt-pkg/error.h>
23 #include <apt-pkg/strutl.h>
24 #include <apt-pkg/fileutl.h>
25
26 // CNC:2002-07-03
27 #include <apt-pkg/repository.h>
28 #include <apt-pkg/md5.h>
29 #include <apt-pkg/sha1.h>
30 #include <config.h>
31 #include <apt-pkg/luaiface.h>
32 #include <iostream>
33 #include <assert.h>
34 using namespace std;
35
36 #include <apti18n.h>
37     
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include <errno.h>
41 #include <string>
42 #include <stdio.h>
43                                                                         /*}}}*/
44
45 using std::string;
46
47 // CNC:2002-07-03
48 // VerifyChecksums - Check MD5 and SHA-1 checksums of a file            /*{{{*/
49 // ---------------------------------------------------------------------
50 /* Returns false only if the checksums fail (the file not existing is not
51    a checksum mismatch) */
52 bool VerifyChecksums(string File,unsigned long Size,string MD5, string method)
53 {
54    struct stat Buf;
55    
56    if (stat(File.c_str(),&Buf) != 0) 
57       return true;
58
59    // LORG:2006-03-09
60    // XXX hack alert: repomd doesn't have index sizes so ignore it and
61    // rely on checksum
62    if (Size > 0 && (unsigned long)Buf.st_size != Size)
63    {
64       if (_config->FindB("Acquire::Verbose", false) == true)
65          cout << "Size of "<<File<<" did not match what's in the checksum list and was redownloaded."<<endl;
66       return false;
67    }
68
69    if (MD5.empty() == false)
70    {
71       if (method == "MD5-Hash") {
72          MD5Summation md5sum = MD5Summation();
73          FileFd F(File, FileFd::ReadOnly);
74          
75          md5sum.AddFD(F.Fd(), F.Size());
76          if (md5sum.Result().Value() != MD5)
77          {
78             if (_config->FindB("Acquire::Verbose", false) == true)
79                cout << "MD5Sum of "<<File<<" did not match what's in the checksum list and was redownloaded."<<endl;
80             return false;
81          }
82       } else if (method == "SHA1-Hash") {
83          SHA1Summation sha1sum = SHA1Summation();
84          FileFd F(File, FileFd::ReadOnly);
85          
86          sha1sum.AddFD(F.Fd(), F.Size());
87          if (sha1sum.Result().Value() != MD5)
88          {
89             if (_config->FindB("Acquire::Verbose", false) == true)
90                cout << "SHASum of "<<File<<" did not match what's in the checksum list and was redownloaded."<<endl;
91             return false;
92          }
93       } 
94          
95    }
96    
97    return true;
98 }
99                                                                         /*}}}*/
100 // Acquire::Item::Item - Constructor                                    /*{{{*/
101 // ---------------------------------------------------------------------
102 /* */
103 pkgAcquire::Item::Item(pkgAcquire *Owner) : Owner(Owner), FileSize(0),
104                        PartialSize(0), Mode(0), ID(0), Complete(false), 
105                        Local(false), QueueCounter(0)
106 {
107    Owner->Add(this);
108    Status = StatIdle;
109 }
110                                                                         /*}}}*/
111 // Acquire::Item::~Item - Destructor                                    /*{{{*/
112 // ---------------------------------------------------------------------
113 /* */
114 pkgAcquire::Item::~Item()
115 {
116    Owner->Remove(this);
117 }
118                                                                         /*}}}*/
119 // Acquire::Item::Failed - Item failed to download                      /*{{{*/
120 // ---------------------------------------------------------------------
121 /* We return to an idle state if there are still other queues that could
122    fetch this object */
123 void pkgAcquire::Item::Failed(string Message,pkgAcquire::MethodConfig *Cnf)
124 {
125    Status = StatIdle;
126    ErrorText = LookupTag(Message,"Message");
127    if (QueueCounter <= 1)
128    {
129       /* This indicates that the file is not available right now but might
130          be sometime later. If we do a retry cycle then this should be
131          retried [CDROMs] */
132       if (Cnf->LocalOnly == true &&
133           StringToBool(LookupTag(Message,"Transient-Failure"),false) == true)
134       {
135          Status = StatIdle;
136          Dequeue();
137          return;
138       }
139       
140       Status = StatError;
141       Dequeue();
142    }   
143 }
144                                                                         /*}}}*/
145 // Acquire::Item::Start - Item has begun to download                    /*{{{*/
146 // ---------------------------------------------------------------------
147 /* Stash status and the file size. Note that setting Complete means 
148    sub-phases of the acquire process such as decompresion are operating */
149 void pkgAcquire::Item::Start(string /*Message*/,unsigned long Size)
150 {
151    Status = StatFetching;
152    if (FileSize == 0 && Complete == false)
153       FileSize = Size;
154 }
155                                                                         /*}}}*/
156 // Acquire::Item::Done - Item downloaded OK                             /*{{{*/
157 // ---------------------------------------------------------------------
158 /* */
159 void pkgAcquire::Item::Done(string Message,unsigned long Size,string,
160                             pkgAcquire::MethodConfig *Cnf)
161 {
162    // We just downloaded something..
163    string FileName = LookupTag(Message,"Filename");
164    if (Complete == false && FileName == DestFile)
165    {
166       if (Owner->Log != 0)
167          Owner->Log->Fetched(Size,atoi(LookupTag(Message,"Resume-Point","0").c_str()));
168    }
169
170    if (FileSize == 0)
171       FileSize= Size;
172    
173    Status = StatDone;
174    ErrorText = string();
175    Owner->Dequeue(this);
176 }
177                                                                         /*}}}*/
178 // Acquire::Item::Rename - Rename a file                                /*{{{*/
179 // ---------------------------------------------------------------------
180 /* This helper function is used by alot of item methods as thier final
181    step */
182 void pkgAcquire::Item::Rename(string From,string To)
183 {
184    if (rename(From.c_str(),To.c_str()) != 0)
185    {
186       char S[300];
187       snprintf(S,sizeof(S),_("rename failed, %s (%s -> %s)."),strerror(errno),
188               From.c_str(),To.c_str());
189       Status = StatError;
190       ErrorText = S;
191    }   
192 }
193                                                                         /*}}}*/
194
195 // AcqIndex::AcqIndex - Constructor                                     /*{{{*/
196 // ---------------------------------------------------------------------
197 /* The package file is added to the queue and a second class is 
198    instantiated to fetch the revision file */   
199 // CNC:2002-07-03
200 pkgAcqIndex::pkgAcqIndex(pkgAcquire *Owner,pkgRepository *Repository,
201                          string URI,string URIDesc,string ShortDesc) :
202                       Item(Owner), RealURI(URI), Repository(Repository)
203 {
204    Decompression = false;
205    Erase = false;
206    
207    DestFile = _config->FindDir("Dir::State::lists") + "partial/";
208    DestFile += URItoFileName(URI);
209
210    // Create the item
211    // CNC:2002-07-03
212    Desc.URI = URI + "." + Repository->GetComprMethod();
213    Desc.Description = URIDesc;
214    Desc.Owner = this;
215    Desc.ShortDesc = ShortDesc;
216       
217    // CNC:2002-07-03
218    // If we're verifying authentication, check whether the size and
219    // checksums match, if not, delete the cached files and force redownload
220    string MD5Hash;
221    unsigned long Size;
222
223    if (Repository != NULL)
224    {
225       if (Repository->HasRelease() == true)
226       {
227          if (Repository->FindChecksums(RealURI,Size,MD5Hash) == false)
228          {
229             if (Repository->IsAuthenticated() == true)
230             {
231                _error->Error(_("%s is not listed in the checksum list for its repository"),
232                              RealURI.c_str());
233                return;
234             }
235             else
236                _error->Warning("Release file did not contain checksum information for %s",
237                                RealURI.c_str());
238          }
239
240          string FinalFile = _config->FindDir("Dir::State::lists");
241          FinalFile += URItoFileName(RealURI);
242
243          if (VerifyChecksums(FinalFile,Size,MD5Hash,Repository->GetCheckMethod()) == false)
244          {
245             unlink(FinalFile.c_str());
246             unlink(DestFile.c_str());
247          }
248       }
249       else if (Repository->IsAuthenticated() == true)
250       {
251          _error->Error(_("Release information not available for %s"),
252                        URI.c_str());
253          return;
254       }
255    }
256
257    QueueURI(Desc);
258 }
259                                                                         /*}}}*/
260 string pkgAcqIndex::ChecksumType()
261 {
262    return Repository->GetCheckMethod();
263 }
264    
265 // AcqIndex::Custom600Headers - Insert custom request headers           /*{{{*/
266 // ---------------------------------------------------------------------
267 /* The only header we use is the last-modified header. */
268 string pkgAcqIndex::Custom600Headers()
269 {
270    string Final = _config->FindDir("Dir::State::lists");
271    Final += URItoFileName(RealURI);
272    
273    struct stat Buf;
274    if (stat(Final.c_str(),&Buf) != 0)
275       return "\nIndex-File: true";
276    
277    return "\nIndex-File: true\nLast-Modified: " + TimeRFC1123(Buf.st_mtime);
278 }
279                                                                         /*}}}*/
280 // AcqIndex::Done - Finished a fetch                                    /*{{{*/
281 // ---------------------------------------------------------------------
282 /* This goes through a number of states.. On the initial fetch the
283    method could possibly return an alternate filename which points
284    to the uncompressed version of the file. If this is so the file
285    is copied into the partial directory. In all other cases the file
286    is decompressed with a gzip uri. */
287 void pkgAcqIndex::Done(string Message,unsigned long Size,string MD5,
288                        pkgAcquire::MethodConfig *Cfg)
289 {
290    Item::Done(Message,Size,MD5,Cfg);
291
292    if (Decompression == true)
293    {
294       // CNC:2002-07-03
295       unsigned long FSize;
296       string MD5Hash;
297
298       if (Repository != NULL && Repository->HasRelease() == true &&
299           Repository->FindChecksums(RealURI,FSize,MD5Hash) == true)
300       {
301          // We must always get here if the repository is authenticated
302          
303          // LORG:2006-03-09
304          // XXX hack alert: repomd doesn't know index sizes so it returns
305          // zero for them, don't check but rely on checksums instead
306          if (FSize > 0 && FSize != Size)
307          {
308             Status = StatError;
309             ErrorText = _("Size mismatch");
310             Rename(DestFile,DestFile + ".FAILED");
311             if (_config->FindB("Acquire::Verbose",false) == true) 
312                _error->Warning("Size mismatch of index file %s: %lu was supposed to be %lu",
313                                RealURI.c_str(), Size, FSize);
314             return;
315          }
316             
317          if (MD5.empty() == false && MD5Hash != MD5)
318          {
319             Status = StatError;
320             ErrorText = _("MD5Sum mismatch");
321             Rename(DestFile,DestFile + ".FAILED");
322             if (_config->FindB("Acquire::Verbose",false) == true) 
323                _error->Warning("MD5Sum mismatch of index file %s: %s was supposed to be %s",
324                                RealURI.c_str(), MD5.c_str(), MD5Hash.c_str());
325             return;
326          }
327       }
328       else
329       {
330          // Redundant security check
331          assert(Repository == NULL || Repository->IsAuthenticated() == false);
332       }
333          
334       // Done, move it into position
335       string FinalFile = _config->FindDir("Dir::State::lists");
336       FinalFile += URItoFileName(RealURI);
337       Rename(DestFile,FinalFile);
338       chmod(FinalFile.c_str(),0644);
339       
340       /* We restore the original name to DestFile so that the clean operation
341          will work OK */
342       DestFile = _config->FindDir("Dir::State::lists") + "partial/";
343       DestFile += URItoFileName(RealURI);
344       
345       // Remove the compressed version.
346       if (Erase == true)
347          unlink(DestFile.c_str());
348       return;
349    }
350
351    Erase = false;
352    Complete = true;
353    
354    // Handle the unzipd case
355    string FileName = LookupTag(Message,"Alt-Filename");
356    if (FileName.empty() == false)
357    {
358       // The files timestamp matches
359       if (StringToBool(LookupTag(Message,"Alt-IMS-Hit"),false) == true)
360          return;
361       
362       Decompression = true;
363       Local = true;
364       DestFile += ".decomp";
365       Desc.URI = "copy:" + FileName;
366       QueueURI(Desc);
367       Mode = "copy";
368       return;
369    }
370
371    FileName = LookupTag(Message,"Filename");
372    if (FileName.empty() == true)
373    {
374       Status = StatError;
375       ErrorText = "Method gave a blank filename";
376    }
377    
378    // The files timestamp matches
379    if (StringToBool(LookupTag(Message,"IMS-Hit"),false) == true)
380       return;
381
382    if (FileName == DestFile)
383       Erase = true;
384    else
385       Local = true;
386    
387    Decompression = true;
388    DestFile += ".decomp";
389    // LORG:2006-02-23 compression is a feature of repository type
390    if (Repository->GetComprMethod() == "gz") {
391       Desc.URI = "gzip:" + FileName;
392       Mode = "gzip";
393    } else if (Repository->GetComprMethod() == "bz2") {
394       Desc.URI = "bzip2:" + FileName;
395       Mode = "bzip2";
396    } else {
397       _error->Warning(_("Unknown compression extension, trying uncompressed"));
398       Desc.URI = FileName;
399    }
400    QueueURI(Desc);
401 }
402                                                                         /*}}}*/
403
404 // AcqIndexRel::pkgAcqIndexRel - Constructor                            /*{{{*/
405 // ---------------------------------------------------------------------
406 /* The Release file is added to the queue */
407 // CNC:2002-07-03
408 pkgAcqIndexRel::pkgAcqIndexRel(pkgAcquire *Owner,pkgRepository *Repository,
409                                string URI,string URIDesc,string ShortDesc,
410                                bool Master) :
411                       Item(Owner), RealURI(URI), Master(Master),
412                       Repository(Repository)
413 {
414    // CNC:2002-07-09
415    assert(Master == false || Repository != NULL);
416    Authentication = false;
417    Erase = false;
418
419    DestFile = _config->FindDir("Dir::State::lists") + "partial/";
420    DestFile += URItoFileName(URI);
421    
422    // Create the item
423    Desc.URI = URI;
424    Desc.Description = URIDesc;
425    Desc.ShortDesc = ShortDesc;
426    Desc.Owner = this;
427
428    // CNC:2002-07-09
429    string MD5Hash;
430    unsigned long Size;
431    if (Master == false && Repository != NULL)
432    {
433       if (Repository->HasRelease() == true)
434       {
435          if (Repository->FindChecksums(RealURI,Size,MD5Hash) == false)
436          {
437             if (Repository->IsAuthenticated() == true)
438             {
439                _error->Error(_("%s is not listed in the checksum list for its repository"),
440                              RealURI.c_str());
441                return;
442             }
443             else
444                _error->Warning("Release file did not contain checksum information for %s",
445                                RealURI.c_str());
446          }
447
448          string FinalFile = _config->FindDir("Dir::State::lists");
449          FinalFile += URItoFileName(RealURI);
450
451          if (VerifyChecksums(FinalFile,Size,MD5Hash,Repository->GetCheckMethod()) == false)
452          {
453             unlink(FinalFile.c_str());
454             unlink(DestFile.c_str()); // Necessary?
455          }
456       }
457       else if (Repository->IsAuthenticated() == true)
458       {
459          _error->Error(_("Release information not available for %s"),
460                        URI.c_str());
461          return;
462       }
463    }
464
465    QueueURI(Desc);
466 }
467                                                                         /*}}}*/
468 // AcqIndexRel::Custom600Headers - Insert custom request headers        /*{{{*/
469 // ---------------------------------------------------------------------
470 /* The only header we use is the last-modified header. */
471 string pkgAcqIndexRel::Custom600Headers()
472 {
473    string Final = _config->FindDir("Dir::State::lists");
474    Final += URItoFileName(RealURI);
475    
476    struct stat Buf;
477    if (stat(Final.c_str(),&Buf) != 0)
478       return "\nIndex-File: true";
479
480    // CNC:2002-07-11
481    string LOI = "";
482    if (Master == true)
483       LOI = "\nLocal-Only-IMS: true";
484    return LOI + "\nIndex-File: true\nLast-Modified: " + TimeRFC1123(Buf.st_mtime);
485 }
486                                                                         /*}}}*/
487 // AcqIndexRel::Done - Item downloaded OK                               /*{{{*/
488 // ---------------------------------------------------------------------
489 /* The release file was not placed into the download directory then
490    a copy URI is generated and it is copied there otherwise the file
491    in the partial directory is moved into .. and the URI is finished. */
492 void pkgAcqIndexRel::Done(string Message,unsigned long Size,string MD5,
493                           pkgAcquire::MethodConfig *Cfg)
494 {
495    Item::Done(Message,Size,MD5,Cfg);
496
497    // CNC:2002-07-03
498    if (Authentication == true) 
499    {
500       if (Repository->IsAuthenticated() == true)
501       {
502          // Do the fingerprint matching magic
503          string FingerPrint = LookupTag(Message,"Signature-Fingerprint");
504
505          if (FingerPrint.empty() == true)
506          {
507             Status = StatError;
508             ErrorText = _("No valid signatures found in Release file");
509             return;
510          }
511
512          // Match fingerprint of Release file
513          if (Repository->FingerPrint != FingerPrint)
514          {
515             Status = StatError;
516             ErrorText = _("Signature fingerprint of Release file does not match (expected ")
517                +Repository->FingerPrint+_(", got ")+FingerPrint+")";
518             return;
519          }
520       }
521
522       // Done, move it into position
523       string FinalFile = _config->FindDir("Dir::State::lists");
524       FinalFile += URItoFileName(RealURI);
525       Rename(DestFile,FinalFile);
526       chmod(FinalFile.c_str(),0644);
527
528       /* We restore the original name to DestFile so that the clean operation
529          will work OK */
530       DestFile = _config->FindDir("Dir::State::lists") + "partial/";
531       DestFile += URItoFileName(RealURI);
532       
533       // Remove the compressed version.
534       if (Erase == true)
535          unlink(DestFile.c_str());
536
537       // Update the hashes and file sizes for this repository
538       if (Repository->ParseRelease(FinalFile) == false && 
539           Repository->IsAuthenticated() == true)
540       {
541          Status = StatError;
542          ErrorText = _("Could not read checksum list from Release file");
543       }
544       return;
545    }
546    
547    string FileName = LookupTag(Message,"Filename");
548    if (FileName.empty() == true)
549    {
550       Status = StatError;
551       ErrorText = "Method gave a blank filename";
552       return;
553    }
554
555    // CNC:2002-07-11
556    Erase = false;
557    Complete = true;
558    
559    // The files timestamp matches
560    if (StringToBool(LookupTag(Message,"IMS-Hit"),false) == true)
561    {
562       // CNC:2002-07-11
563       if (Master == true)
564       {
565          // We've got a LocalOnly IMS
566          string FinalFile = _config->FindDir("Dir::State::lists");
567          FinalFile += URItoFileName(RealURI);
568          Repository->ParseRelease(FinalFile);
569       }
570       return;
571    }
572    
573    // We have to copy it into place
574    if (FileName != DestFile)
575    {
576       Local = true;
577       Desc.URI = "copy:" + FileName;
578       QueueURI(Desc);
579       return;
580    }
581    
582    // CNC:2002-07-03
583    unsigned long FSize;
584    string MD5Hash;
585    if (Master == false && Repository != NULL
586        && Repository->HasRelease() == true
587        && Repository->FindChecksums(RealURI,FSize,MD5Hash) == true)
588    {
589       if (FSize != Size)
590       {
591          Status = StatError;
592          ErrorText = _("Size mismatch");
593          Rename(DestFile,DestFile + ".FAILED");
594          if (_config->FindB("Acquire::Verbose",false) == true) 
595             _error->Warning("Size mismatch of index file %s: %lu was supposed to be %lu",
596                             RealURI.c_str(), Size, FSize);
597          return;
598       }
599       if (MD5.empty() == false && MD5Hash != MD5)
600       {
601          Status = StatError;
602          ErrorText = _("MD5Sum mismatch");
603          Rename(DestFile,DestFile + ".FAILED");
604          if (_config->FindB("Acquire::Verbose",false) == true) 
605             _error->Warning("MD5Sum mismatch of index file %s: %s was supposed to be %s",
606                             RealURI.c_str(), MD5.c_str(), MD5Hash.c_str());
607          return;
608       }
609    }
610
611    if (Master == false || Repository->IsAuthenticated() == false)
612    {
613       // Done, move it into position
614       string FinalFile = _config->FindDir("Dir::State::lists");
615       FinalFile += URItoFileName(RealURI);
616       Rename(DestFile,FinalFile);
617       chmod(FinalFile.c_str(),0644);
618
619       // extract checksums from the Release file
620       if (Master == true)
621          Repository->ParseRelease(FinalFile);
622    }
623    else 
624    {
625       if (FileName == DestFile)
626          Erase = true;
627       else
628          Local = true;
629    
630       // Still have the authentication phase
631       Authentication = true;
632       DestFile += ".auth";
633       Desc.URI = "gpg:" + FileName;
634       QueueURI(Desc);
635       Mode = "gpg";
636    }
637 }
638                                                                         /*}}}*/
639 // AcqIndexRel::Failed - Silence failure messages for missing rel files /*{{{*/
640 // ---------------------------------------------------------------------
641 /* */
642 void pkgAcqIndexRel::Failed(string Message,pkgAcquire::MethodConfig *Cnf)
643 {
644    if (Cnf->LocalOnly == true || 
645        StringToBool(LookupTag(Message,"Transient-Failure"),false) == false)
646    {      
647       // CNC:2002-07-03
648       if (Master == false || Repository->IsAuthenticated() == false)
649       {
650          // Ignore this
651          Status = StatDone;
652          Complete = false;
653          Dequeue();
654          return;
655       }
656    }
657    
658    Item::Failed(Message,Cnf);
659 }
660                                                                         /*}}}*/
661
662 // AcqArchive::AcqArchive - Constructor                                 /*{{{*/
663 // ---------------------------------------------------------------------
664 /* This just sets up the initial fetch environment and queues the first
665    possibilitiy */
666 pkgAcqArchive::pkgAcqArchive(pkgAcquire *Owner,pkgSourceList *Sources,
667                              pkgRecords *Recs,pkgCache::VerIterator const &Version,
668                              string &StoreFilename) :
669                Item(Owner), Version(Version), Sources(Sources), Recs(Recs), 
670                StoreFilename(StoreFilename), Vf(Version.FileList())
671 {
672    Retries = _config->FindI("Acquire::Retries",0);
673
674    ChkType = "";
675
676    if (Version.Arch() == 0)
677    {
678       _error->Error(_("I wasn't able to locate a file for the %s package. "
679                       "This might mean you need to manually fix this package. "
680                       "(due to missing arch)"),
681                     Version.ParentPkg().Name());
682       return;
683    }
684    
685    /* We need to find a filename to determine the extension. We make the
686       assumption here that all the available sources for this version share
687       the same extension.. */
688    // Skip not source sources, they do not have file fields.
689    for (; Vf.end() == false; Vf++)
690    {
691       if ((Vf.File()->Flags & pkgCache::Flag::NotSource) != 0)
692          continue;
693       break;
694    }
695    
696    // Does not really matter here.. we are going to fail out below
697    if (Vf.end() != true)
698    {     
699       // If this fails to get a file name we will bomb out below.
700       pkgRecords::Parser &Parse = Recs->Lookup(Vf);
701       if (_error->PendingError() == true)
702          return;
703             
704       // Generate the final file name as: package_version_arch.foo
705       if (_config->FindB("Acquire::Munge-Filenames", false) == true) {
706          StoreFilename = QuoteString(Version.ParentPkg().Name(),"_:") + '_' +
707                          QuoteString(Version.VerStr(),"_:") + '_' +
708                          QuoteString(Version.Arch(),"_:.") + 
709                          "." + flExtension(Parse.FileName());
710       } else {
711          StoreFilename = Parse.FileName();
712       }
713    }
714       
715    // Select a source
716    if (QueueNext() == false && _error->PendingError() == false)
717       _error->Error(_("I wasn't able to locate file for the %s package. "
718                     "This might mean you need to manually fix this package."),
719                     Version.ParentPkg().Name());
720 }
721                                                                         /*}}}*/
722 // AcqArchive::QueueNext - Queue the next file source                   /*{{{*/
723 // ---------------------------------------------------------------------
724 /* This queues the next available file version for download. It checks if
725    the archive is already available in the cache and stashs the MD5 for
726    checking later. */
727 bool pkgAcqArchive::QueueNext()
728 {   
729    for (; Vf.end() == false; Vf++)
730    {
731       // Ignore not source sources
732       if ((Vf.File()->Flags & pkgCache::Flag::NotSource) != 0)
733          continue;
734
735       // Try to cross match against the source list
736       pkgIndexFile *Index;
737       if (Sources->FindIndex(Vf.File(),Index) == false)
738             continue;
739
740       // Grab the text package record
741       pkgRecords::Parser &Parse = Recs->Lookup(Vf);
742       if (_error->PendingError() == true)
743          return false;
744       
745       string PkgFile = Parse.FileName();
746       // LORG:2006-03-16 
747       // Repomd uses SHA checksums for packages wheras others use MD5..
748       ChkType = Index->ChecksumType();
749       if (Index->ChecksumType() == "SHA1-Hash") {
750          MD5 = Parse.SHA1Hash();
751       } else {
752          MD5 = Parse.MD5Hash();
753       }
754
755       if (PkgFile.empty() == true)
756          return _error->Error(_("The package index files are corrupted. No Filename: "
757                               "field for package %s."),
758                               Version.ParentPkg().Name());
759
760       // See if we already have the file. (Legacy filenames)
761       FileSize = Version->Size;
762       string FinalFile = _config->FindDir("Dir::Cache::Archives") + flNotDir(PkgFile);
763       struct stat Buf;
764       if (stat(FinalFile.c_str(),&Buf) == 0)
765       {
766          // Make sure the size matches
767          if ((unsigned)Buf.st_size == Version->Size)
768          {
769             Complete = true;
770             Local = true;
771             Status = StatDone;
772             StoreFilename = DestFile = FinalFile;
773             return true;
774          }
775          
776          /* Hmm, we have a file and its size does not match, this means it is
777             an old style mismatched arch */
778          unlink(FinalFile.c_str());
779       }
780
781       // Check it again using the new style output filenames
782       FinalFile = _config->FindDir("Dir::Cache::Archives") + flNotDir(StoreFilename);
783       if (stat(FinalFile.c_str(),&Buf) == 0)
784       {
785          // Make sure the size matches
786          if ((unsigned)Buf.st_size == Version->Size)
787          {
788             Complete = true;
789             Local = true;
790             Status = StatDone;
791             StoreFilename = DestFile = FinalFile;
792             return true;
793          }
794          
795          /* Hmm, we have a file and its size does not match, this shouldnt
796             happen.. */
797          unlink(FinalFile.c_str());
798       }
799
800       DestFile = _config->FindDir("Dir::Cache::Archives") + "partial/" + flNotDir(StoreFilename);
801       
802       // Check the destination file
803       if (stat(DestFile.c_str(),&Buf) == 0)
804       {
805          // Hmm, the partial file is too big, erase it
806          if ((unsigned)Buf.st_size > Version->Size)
807             unlink(DestFile.c_str());
808          else
809             PartialSize = Buf.st_size;
810       }
811       
812       // Create the item
813       Local = false;
814       Desc.URI = Index->ArchiveURI(PkgFile);
815       Desc.Description = Index->ArchiveInfo(Version);
816       Desc.Owner = this;
817       Desc.ShortDesc = Version.ParentPkg().Name();
818       QueueURI(Desc);
819
820       Vf++;
821       return true;
822    }
823    return false;
824 }   
825                                                                         /*}}}*/
826
827 // CNC:2003-03-19
828 #ifdef WITH_LUA
829 // ScriptsAcquireDone - Script trigger.                                 /*{{{*/
830 // ---------------------------------------------------------------------
831 /* */
832 template<class T>
833 static void ScriptsAcquireDone(const char *ConfKey,
834                                string &StoreFilename,
835                                string &ErrorText,
836                                T &Status)
837 {
838    if (_lua->HasScripts(ConfKey) == true) {
839       _lua->SetGlobal("acquire_filename", StoreFilename.c_str());
840       _lua->SetGlobal("acquire_error", (const char *)NULL);
841       _lua->RunScripts(ConfKey, true);
842       const char *Error = _lua->GetGlobalStr("acquire_error");
843       if (Error != NULL && *Error != 0) {
844          Status = pkgAcquire::Item::StatError;
845          ErrorText = Error;
846       }
847       _lua->ResetGlobals();
848    }
849 }
850                                                                         /*}}}*/
851 #endif
852
853 // AcqArchive::Done - Finished fetching                                 /*{{{*/
854 // ---------------------------------------------------------------------
855 /* */
856 void pkgAcqArchive::Done(string Message,unsigned long Size,string Md5Hash,
857                          pkgAcquire::MethodConfig *Cfg)
858 {
859    Item::Done(Message,Size,Md5Hash,Cfg);
860    
861    // Check the size
862    if (Size != Version->Size)
863    {
864       Status = StatError;
865       ErrorText = _("Size mismatch");
866       return;
867    }
868    
869    // Check the md5
870    if (Md5Hash.empty() == false && MD5.empty() == false)
871    {
872       if (Md5Hash != MD5)
873       {
874          Status = StatError;
875          ErrorText = _("MD5Sum mismatch");
876          Rename(DestFile,DestFile + ".FAILED");
877          return;
878       }
879    }
880
881    // Grab the output filename
882    string FileName = LookupTag(Message,"Filename");
883    if (FileName.empty() == true)
884    {
885       Status = StatError;
886       ErrorText = "Method gave a blank filename";
887       return;
888    }
889
890    Complete = true;
891
892    // Reference filename
893    if (FileName != DestFile)
894    {
895       StoreFilename = DestFile = FileName;
896       Local = true;
897
898 // CNC:2003-03-19
899 #ifdef WITH_LUA
900       ScriptsAcquireDone("Scripts::Acquire::Archive::Done",
901                          StoreFilename, ErrorText, Status);
902 #endif
903
904       return;
905    }
906    
907    // Done, move it into position
908    string FinalFile = _config->FindDir("Dir::Cache::Archives");
909    FinalFile += flNotDir(StoreFilename);
910    Rename(DestFile,FinalFile);
911    
912    StoreFilename = DestFile = FinalFile;
913    Complete = true;
914
915 // CNC:2003-03-19
916 #ifdef WITH_LUA
917    ScriptsAcquireDone("Scripts::Acquire::Archive::Done",
918                       StoreFilename, ErrorText, Status);
919 #endif
920
921 }
922                                                                         /*}}}*/
923 // AcqArchive::Failed - Failure handler                                 /*{{{*/
924 // ---------------------------------------------------------------------
925 /* Here we try other sources */
926 void pkgAcqArchive::Failed(string Message,pkgAcquire::MethodConfig *Cnf)
927 {
928    ErrorText = LookupTag(Message,"Message");
929    
930    /* We don't really want to retry on failed media swaps, this prevents 
931       that. An interesting observation is that permanent failures are not
932       recorded. */
933    if (Cnf->Removable == true && 
934        StringToBool(LookupTag(Message,"Transient-Failure"),false) == true)
935    {
936       // Vf = Version.FileList();
937       while (Vf.end() == false) Vf++;
938       StoreFilename = string();
939       Item::Failed(Message,Cnf);
940       return;
941    }
942    
943    if (QueueNext() == false)
944    {
945       // This is the retry counter
946       if (Retries != 0 &&
947           Cnf->LocalOnly == false &&
948           StringToBool(LookupTag(Message,"Transient-Failure"),false) == true)
949       {
950          Retries--;
951          Vf = Version.FileList();
952          if (QueueNext() == true)
953             return;
954       }
955       
956       StoreFilename = string();
957       Item::Failed(Message,Cnf);
958    }
959 }
960                                                                         /*}}}*/
961 // AcqArchive::Finished - Fetching has finished, tidy up                /*{{{*/
962 // ---------------------------------------------------------------------
963 /* */
964 void pkgAcqArchive::Finished()
965 {
966    if (Status == pkgAcquire::Item::StatDone &&
967        Complete == true)
968       return;
969    StoreFilename = string();
970 }
971                                                                         /*}}}*/
972
973 // AcqFile::pkgAcqFile - Constructor                                    /*{{{*/
974 // ---------------------------------------------------------------------
975 /* The file is added to the queue */
976 pkgAcqFile::pkgAcqFile(pkgAcquire *Owner,string URI,string MD5,
977                        unsigned long Size,string Dsc,string ShortDesc) :
978                        Item(Owner), Md5Hash(MD5)
979 {
980    Retries = _config->FindI("Acquire::Retries",0);
981    
982    DestFile = flNotDir(URI);
983    
984    // Create the item
985    Desc.URI = URI;
986    Desc.Description = Dsc;
987    Desc.Owner = this;
988
989    // Set the short description to the archive component
990    Desc.ShortDesc = ShortDesc;
991       
992    // Get the transfer sizes
993    FileSize = Size;
994    struct stat Buf;
995    if (stat(DestFile.c_str(),&Buf) == 0)
996    {
997       // Hmm, the partial file is too big, erase it
998       if ((unsigned)Buf.st_size > Size)
999          unlink(DestFile.c_str());
1000       else
1001          PartialSize = Buf.st_size;
1002    }
1003    
1004    QueueURI(Desc);
1005 }
1006                                                                         /*}}}*/
1007 // AcqFile::Done - Item downloaded OK                                   /*{{{*/
1008 // ---------------------------------------------------------------------
1009 /* */
1010 void pkgAcqFile::Done(string Message,unsigned long Size,string MD5,
1011                       pkgAcquire::MethodConfig *Cnf)
1012 {
1013    // Check the md5
1014    if (Md5Hash.empty() == false && MD5.empty() == false)
1015    {
1016       if (Md5Hash != MD5)
1017       {
1018          Status = StatError;
1019          ErrorText = "MD5Sum mismatch";
1020          Rename(DestFile,DestFile + ".FAILED");
1021          return;
1022       }
1023    }
1024    
1025    Item::Done(Message,Size,MD5,Cnf);
1026
1027    string FileName = LookupTag(Message,"Filename");
1028    if (FileName.empty() == true)
1029    {
1030       Status = StatError;
1031       ErrorText = "Method gave a blank filename";
1032       return;
1033    }
1034
1035    Complete = true;
1036    
1037    // The files timestamp matches
1038    if (StringToBool(LookupTag(Message,"IMS-Hit"),false) == true)
1039       return;
1040    
1041    // We have to copy it into place
1042    if (FileName != DestFile)
1043    {
1044       Local = true;
1045       if (_config->FindB("Acquire::Source-Symlinks",true) == false ||
1046           Cnf->Removable == true)
1047       {
1048          Desc.URI = "copy:" + FileName;
1049          QueueURI(Desc);
1050          return;
1051       }
1052       
1053       // Erase the file if it is a symlink so we can overwrite it
1054       struct stat St;
1055       if (lstat(DestFile.c_str(),&St) == 0)
1056       {
1057          if (S_ISLNK(St.st_mode) != 0)
1058             unlink(DestFile.c_str());
1059          // CNC:2003-12-11 - Check if FileName == DestFile
1060          else {
1061             struct stat St2;
1062             if (stat(FileName.c_str(), &St2) == 0
1063                 && St.st_ino == St2.st_ino)
1064                return;
1065          }
1066       }
1067       
1068       // Symlink the file
1069       if (symlink(FileName.c_str(),DestFile.c_str()) != 0)
1070       {
1071          ErrorText = "Link to " + DestFile + " failure ";
1072          Status = StatError;
1073          Complete = false;
1074       }      
1075    }
1076 }
1077                                                                         /*}}}*/
1078 // AcqFile::Failed - Failure handler                                    /*{{{*/
1079 // ---------------------------------------------------------------------
1080 /* Here we try other sources */
1081 void pkgAcqFile::Failed(string Message,pkgAcquire::MethodConfig *Cnf)
1082 {
1083    ErrorText = LookupTag(Message,"Message");
1084    
1085    // This is the retry counter
1086    if (Retries != 0 &&
1087        Cnf->LocalOnly == false &&
1088        StringToBool(LookupTag(Message,"Transient-Failure"),false) == true)
1089    {
1090       Retries--;
1091       QueueURI(Desc);
1092       return;
1093    }
1094    
1095    Item::Failed(Message,Cnf);
1096 }
1097                                                                         /*}}}*/
1098 // vim:sts=3:sw=3