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