Checked the size of partial files #33705
[ntk/apt.git] / apt-pkg / acquire-item.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: acquire-item.cc,v 1.26 1999/03/27 03:02:38 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/error.h>
22 #include <apt-pkg/strutl.h>
23
24 #include <sys/stat.h>
25 #include <unistd.h>
26 #include <errno.h>
27 #include <string.h>
28 #include <stdio.h>
29 /*}}}*/
30
31 // Acquire::Item::Item - Constructor /*{{{*/
32 // ---------------------------------------------------------------------
33 /* */
34 pkgAcquire::Item::Item(pkgAcquire *Owner) : Owner(Owner), FileSize(0),
35 PartialSize(0), Mode(0), ID(0), Complete(false),
36 Local(false), QueueCounter(0)
37 {
38 Owner->Add(this);
39 Status = StatIdle;
40 }
41 /*}}}*/
42 // Acquire::Item::~Item - Destructor /*{{{*/
43 // ---------------------------------------------------------------------
44 /* */
45 pkgAcquire::Item::~Item()
46 {
47 Owner->Remove(this);
48 }
49 /*}}}*/
50 // Acquire::Item::Failed - Item failed to download /*{{{*/
51 // ---------------------------------------------------------------------
52 /* We return to an idle state if there are still other queues that could
53 fetch this object */
54 void pkgAcquire::Item::Failed(string Message,pkgAcquire::MethodConfig *Cnf)
55 {
56 Status = StatIdle;
57 ErrorText = LookupTag(Message,"Message");
58 if (QueueCounter <= 1)
59 {
60 /* This indicates that the file is not available right now but might
61 be sometime later. If we do a retry cycle then this should be
62 retried [CDROMs] */
63 if (Cnf->LocalOnly == true &&
64 StringToBool(LookupTag(Message,"Transient-Failure"),false) == true)
65 {
66 Status = StatIdle;
67 Dequeue();
68 return;
69 }
70
71 Status = StatError;
72 Dequeue();
73 }
74 }
75 /*}}}*/
76 // Acquire::Item::Start - Item has begun to download /*{{{*/
77 // ---------------------------------------------------------------------
78 /* Stash status and the file size. Note that setting Complete means
79 sub-phases of the acquire process such as decompresion are operating */
80 void pkgAcquire::Item::Start(string Message,unsigned long Size)
81 {
82 Status = StatFetching;
83 if (FileSize == 0 && Complete == false)
84 FileSize = Size;
85 }
86 /*}}}*/
87 // Acquire::Item::Done - Item downloaded OK /*{{{*/
88 // ---------------------------------------------------------------------
89 /* */
90 void pkgAcquire::Item::Done(string Message,unsigned long Size,string)
91 {
92 // We just downloaded something..
93 string FileName = LookupTag(Message,"Filename");
94 if (Complete == false && FileName == DestFile)
95 {
96 if (Owner->Log != 0)
97 Owner->Log->Fetched(Size,atoi(LookupTag(Message,"Resume-Point","0").c_str()));
98 }
99
100 Status = StatDone;
101 ErrorText = string();
102 Owner->Dequeue(this);
103 }
104 /*}}}*/
105 // Acquire::Item::Rename - Rename a file /*{{{*/
106 // ---------------------------------------------------------------------
107 /* This helper function is used by alot of item methods as thier final
108 step */
109 void pkgAcquire::Item::Rename(string From,string To)
110 {
111 if (rename(From.c_str(),To.c_str()) != 0)
112 {
113 char S[300];
114 sprintf(S,"rename failed, %s (%s -> %s).",strerror(errno),
115 From.c_str(),To.c_str());
116 Status = StatError;
117 ErrorText = S;
118 }
119 }
120 /*}}}*/
121
122 // AcqIndex::AcqIndex - Constructor /*{{{*/
123 // ---------------------------------------------------------------------
124 /* The package file is added to the queue and a second class is
125 instantiated to fetch the revision file */
126 pkgAcqIndex::pkgAcqIndex(pkgAcquire *Owner,const pkgSourceList::Item *Location) :
127 Item(Owner), Location(Location)
128 {
129 Decompression = false;
130 Erase = false;
131
132 DestFile = _config->FindDir("Dir::State::lists") + "partial/";
133 DestFile += URItoFileName(Location->PackagesURI());
134
135 // Create the item
136 Desc.URI = Location->PackagesURI() + ".gz";
137 Desc.Description = Location->PackagesInfo();
138 Desc.Owner = this;
139
140 // Set the short description to the archive component
141 if (Location->Dist[Location->Dist.size() - 1] == '/')
142 Desc.ShortDesc = Location->Dist;
143 else
144 Desc.ShortDesc = Location->Dist + '/' + Location->Section;
145
146 QueueURI(Desc);
147
148 // Create the Release fetch class
149 new pkgAcqIndexRel(Owner,Location);
150 }
151 /*}}}*/
152 // AcqIndex::Custom600Headers - Insert custom request headers /*{{{*/
153 // ---------------------------------------------------------------------
154 /* The only header we use is the last-modified header. */
155 string pkgAcqIndex::Custom600Headers()
156 {
157 string Final = _config->FindDir("Dir::State::lists");
158 Final += URItoFileName(Location->PackagesURI());
159
160 struct stat Buf;
161 if (stat(Final.c_str(),&Buf) != 0)
162 return "\nIndex-File: true";
163
164 return "\nIndex-File: true\nLast-Modified: " + TimeRFC1123(Buf.st_mtime);
165 }
166 /*}}}*/
167 // AcqIndex::Done - Finished a fetch /*{{{*/
168 // ---------------------------------------------------------------------
169 /* This goes through a number of states.. On the initial fetch the
170 method could possibly return an alternate filename which points
171 to the uncompressed version of the file. If this is so the file
172 is copied into the partial directory. In all other cases the file
173 is decompressed with a gzip uri. */
174 void pkgAcqIndex::Done(string Message,unsigned long Size,string MD5)
175 {
176 Item::Done(Message,Size,MD5);
177
178 if (Decompression == true)
179 {
180 // Done, move it into position
181 string FinalFile = _config->FindDir("Dir::State::lists");
182 FinalFile += URItoFileName(Location->PackagesURI());
183 Rename(DestFile,FinalFile);
184
185 /* We restore the original name to DestFile so that the clean operation
186 will work OK */
187 DestFile = _config->FindDir("Dir::State::lists") + "partial/";
188 DestFile += URItoFileName(Location->PackagesURI());
189
190 // Remove the compressed version.
191 if (Erase == true)
192 unlink(DestFile.c_str());
193 return;
194 }
195
196 Erase = false;
197 Complete = true;
198
199 // Handle the unzipd case
200 string FileName = LookupTag(Message,"Alt-Filename");
201 if (FileName.empty() == false)
202 {
203 // The files timestamp matches
204 if (StringToBool(LookupTag(Message,"Alt-IMS-Hit"),false) == true)
205 return;
206
207 Decompression = true;
208 Local = true;
209 DestFile += ".decomp";
210 Desc.URI = "copy:" + FileName;
211 QueueURI(Desc);
212 Mode = "copy";
213 return;
214 }
215
216 FileName = LookupTag(Message,"Filename");
217 if (FileName.empty() == true)
218 {
219 Status = StatError;
220 ErrorText = "Method gave a blank filename";
221 }
222
223 // The files timestamp matches
224 if (StringToBool(LookupTag(Message,"IMS-Hit"),false) == true)
225 return;
226
227 if (FileName == DestFile)
228 Erase = true;
229 else
230 Local = true;
231
232 Decompression = true;
233 DestFile += ".decomp";
234 Desc.URI = "gzip:" + FileName,Location->PackagesInfo();
235 QueueURI(Desc);
236 Mode = "gzip";
237 }
238 /*}}}*/
239 // AcqIndex::Describe - Describe the Item /*{{{*/
240 // ---------------------------------------------------------------------
241 /* */
242 string pkgAcqIndex::Describe()
243 {
244 return Location->PackagesURI();
245 }
246 /*}}}*/
247
248 // AcqIndexRel::pkgAcqIndexRel - Constructor /*{{{*/
249 // ---------------------------------------------------------------------
250 /* The Release file is added to the queue */
251 pkgAcqIndexRel::pkgAcqIndexRel(pkgAcquire *Owner,
252 const pkgSourceList::Item *Location) :
253 Item(Owner), Location(Location)
254 {
255 DestFile = _config->FindDir("Dir::State::lists") + "partial/";
256 DestFile += URItoFileName(Location->ReleaseURI());
257
258 // Create the item
259 Desc.URI = Location->ReleaseURI();
260 Desc.Description = Location->ReleaseInfo();
261 Desc.Owner = this;
262
263 // Set the short description to the archive component
264 if (Location->Dist[Location->Dist.size() - 1] == '/')
265 Desc.ShortDesc = Location->Dist;
266 else
267 Desc.ShortDesc = Location->Dist + '/' + Location->Section;
268
269 QueueURI(Desc);
270 }
271 /*}}}*/
272 // AcqIndexRel::Custom600Headers - Insert custom request headers /*{{{*/
273 // ---------------------------------------------------------------------
274 /* The only header we use is the last-modified header. */
275 string pkgAcqIndexRel::Custom600Headers()
276 {
277 string Final = _config->FindDir("Dir::State::lists");
278 Final += URItoFileName(Location->ReleaseURI());
279
280 struct stat Buf;
281 if (stat(Final.c_str(),&Buf) != 0)
282 return "\nIndex-File: true";
283
284 return "\nIndex-File: true\nLast-Modified: " + TimeRFC1123(Buf.st_mtime);
285 }
286 /*}}}*/
287 // AcqIndexRel::Done - Item downloaded OK /*{{{*/
288 // ---------------------------------------------------------------------
289 /* The release file was not placed into the download directory then
290 a copy URI is generated and it is copied there otherwise the file
291 in the partial directory is moved into .. and the URI is finished. */
292 void pkgAcqIndexRel::Done(string Message,unsigned long Size,string MD5)
293 {
294 Item::Done(Message,Size,MD5);
295
296 string FileName = LookupTag(Message,"Filename");
297 if (FileName.empty() == true)
298 {
299 Status = StatError;
300 ErrorText = "Method gave a blank filename";
301 return;
302 }
303
304 Complete = true;
305
306 // The files timestamp matches
307 if (StringToBool(LookupTag(Message,"IMS-Hit"),false) == true)
308 return;
309
310 // We have to copy it into place
311 if (FileName != DestFile)
312 {
313 Local = true;
314 Desc.URI = "copy:" + FileName;
315 QueueURI(Desc);
316 return;
317 }
318
319 // Done, move it into position
320 string FinalFile = _config->FindDir("Dir::State::lists");
321 FinalFile += URItoFileName(Location->ReleaseURI());
322 Rename(DestFile,FinalFile);
323 }
324 /*}}}*/
325 // AcqIndexRel::Describe - Describe the Item /*{{{*/
326 // ---------------------------------------------------------------------
327 /* */
328 string pkgAcqIndexRel::Describe()
329 {
330 return Location->ReleaseURI();
331 }
332 /*}}}*/
333 // AcqIndexRel::Failed - Silence failure messages for missing rel files /*{{{*/
334 // ---------------------------------------------------------------------
335 /* */
336 void pkgAcqIndexRel::Failed(string Message,pkgAcquire::MethodConfig *Cnf)
337 {
338 // This is the retry counter
339 if (Cnf->LocalOnly == true ||
340 StringToBool(LookupTag(Message,"Transient-Failure"),false) == false)
341 {
342 // Ignore this
343 Status = StatDone;
344 Complete = false;
345 Dequeue();
346 return;
347 }
348
349 Item::Failed(Message,Cnf);
350 }
351 /*}}}*/
352
353 // AcqArchive::AcqArchive - Constructor /*{{{*/
354 // ---------------------------------------------------------------------
355 /* This just sets up the initial fetch environment and queues the first
356 possibilitiy */
357 pkgAcqArchive::pkgAcqArchive(pkgAcquire *Owner,pkgSourceList *Sources,
358 pkgRecords *Recs,pkgCache::VerIterator const &Version,
359 string &StoreFilename) :
360 Item(Owner), Version(Version), Sources(Sources), Recs(Recs),
361 StoreFilename(StoreFilename), Vf(Version.FileList())
362 {
363 Retries = _config->FindI("Acquire::Retries",0);
364
365 // Generate the final file name as: package_version_arch.deb
366 StoreFilename = QuoteString(Version.ParentPkg().Name(),"_:") + '_' +
367 QuoteString(Version.VerStr(),"_:") + '_' +
368 QuoteString(Version.Arch(),"_:.") + ".deb";
369
370 // Select a source
371 if (QueueNext() == false && _error->PendingError() == false)
372 _error->Error("I wasn't able to locate file for the %s package. "
373 "This might mean you need to manually fix this package.",
374 Version.ParentPkg().Name());
375 }
376 /*}}}*/
377 // AcqArchive::QueueNext - Queue the next file source /*{{{*/
378 // ---------------------------------------------------------------------
379 /* This queues the next available file version for download. It checks if
380 the archive is already available in the cache and stashs the MD5 for
381 checking later. */
382 bool pkgAcqArchive::QueueNext()
383 {
384 for (; Vf.end() == false; Vf++)
385 {
386 // Ignore not source sources
387 if ((Vf.File()->Flags & pkgCache::Flag::NotSource) != 0)
388 continue;
389
390 // Try to cross match against the source list
391 string PkgFile = flNotDir(Vf.File().FileName());
392 pkgSourceList::const_iterator Location;
393 for (Location = Sources->begin(); Location != Sources->end(); Location++)
394 if (PkgFile == URItoFileName(Location->PackagesURI()))
395 break;
396
397 if (Location == Sources->end())
398 continue;
399
400 // Grab the text package record
401 pkgRecords::Parser &Parse = Recs->Lookup(Vf);
402 if (_error->PendingError() == true)
403 return false;
404
405 PkgFile = Parse.FileName();
406 MD5 = Parse.MD5Hash();
407 if (PkgFile.empty() == true)
408 return _error->Error("The package index files are corrupted. No Filename: "
409 "field for package %s."
410 ,Version.ParentPkg().Name());
411
412 // See if we already have the file. (Legacy filenames)
413 FileSize = Version->Size;
414 string FinalFile = _config->FindDir("Dir::Cache::Archives") + flNotDir(PkgFile);
415 struct stat Buf;
416 if (stat(FinalFile.c_str(),&Buf) == 0)
417 {
418 // Make sure the size matches
419 if ((unsigned)Buf.st_size == Version->Size)
420 {
421 Complete = true;
422 Local = true;
423 Status = StatDone;
424 StoreFilename = DestFile = FinalFile;
425 return true;
426 }
427
428 /* Hmm, we have a file and its size does not match, this means it is
429 an old style mismatched arch */
430 unlink(FinalFile.c_str());
431 }
432
433 // Check it again using the new style output filenames
434 FinalFile = _config->FindDir("Dir::Cache::Archives") + flNotDir(StoreFilename);
435 if (stat(FinalFile.c_str(),&Buf) == 0)
436 {
437 // Make sure the size matches
438 if ((unsigned)Buf.st_size == Version->Size)
439 {
440 Complete = true;
441 Local = true;
442 Status = StatDone;
443 StoreFilename = DestFile = FinalFile;
444 return true;
445 }
446
447 /* Hmm, we have a file and its size does not match, this shouldnt
448 happen.. */
449 unlink(FinalFile.c_str());
450 }
451
452 DestFile = _config->FindDir("Dir::Cache::Archives") + "partial/" + flNotDir(StoreFilename);
453
454 // Check the destination file
455 if (stat(DestFile.c_str(),&Buf) == 0)
456 {
457 // Hmm, the partial file is too big, erase it
458 if ((unsigned)Buf.st_size > Version->Size)
459 unlink(DestFile.c_str());
460 else
461 PartialSize = Buf.st_size;
462 }
463
464 // Create the item
465 Desc.URI = Location->ArchiveURI(PkgFile);
466 Desc.Description = Location->ArchiveInfo(Version);
467 Desc.Owner = this;
468 Desc.ShortDesc = Version.ParentPkg().Name();
469 QueueURI(Desc);
470
471 Vf++;
472 return true;
473 }
474 return false;
475 }
476 /*}}}*/
477 // AcqArchive::Done - Finished fetching /*{{{*/
478 // ---------------------------------------------------------------------
479 /* */
480 void pkgAcqArchive::Done(string Message,unsigned long Size,string Md5Hash)
481 {
482 Item::Done(Message,Size,Md5Hash);
483
484 // Check the size
485 if (Size != Version->Size)
486 {
487 _error->Error("Size mismatch for package %s",Version.ParentPkg().Name());
488 return;
489 }
490
491 // Check the md5
492 if (Md5Hash.empty() == false && MD5.empty() == false)
493 {
494 if (Md5Hash != MD5)
495 {
496 _error->Error("MD5Sum mismatch for package %s",Version.ParentPkg().Name());
497 return;
498 }
499 }
500
501 // Grab the output filename
502 string FileName = LookupTag(Message,"Filename");
503 if (FileName.empty() == true)
504 {
505 Status = StatError;
506 ErrorText = "Method gave a blank filename";
507 return;
508 }
509
510 Complete = true;
511
512 // Reference filename
513 if (FileName != DestFile)
514 {
515 StoreFilename = DestFile = FileName;
516 Local = true;
517 return;
518 }
519
520 // Done, move it into position
521 string FinalFile = _config->FindDir("Dir::Cache::Archives");
522 FinalFile += flNotDir(StoreFilename);
523 Rename(DestFile,FinalFile);
524
525 StoreFilename = DestFile = FinalFile;
526 Complete = true;
527 }
528 /*}}}*/
529 // AcqArchive::Describe - Describe the Item /*{{{*/
530 // ---------------------------------------------------------------------
531 /* */
532 string pkgAcqArchive::Describe()
533 {
534 return Desc.URI;
535 }
536 /*}}}*/
537 // AcqArchive::Failed - Failure handler /*{{{*/
538 // ---------------------------------------------------------------------
539 /* Here we try other sources */
540 void pkgAcqArchive::Failed(string Message,pkgAcquire::MethodConfig *Cnf)
541 {
542 ErrorText = LookupTag(Message,"Message");
543 if (QueueNext() == false)
544 {
545 // This is the retry counter
546 if (Retries != 0 &&
547 Cnf->LocalOnly == false &&
548 StringToBool(LookupTag(Message,"Transient-Failure"),false) == true)
549 {
550 Retries--;
551 Vf = Version.FileList();
552 if (QueueNext() == true)
553 return;
554 }
555
556 StoreFilename = string();
557 Item::Failed(Message,Cnf);
558 }
559 }
560 /*}}}*/