]> git.saurik.com Git - apt.git/blame_incremental - apt-pkg/acquire-worker.cc
deal better with acquiring the same URI multiple times
[apt.git] / apt-pkg / acquire-worker.cc
... / ...
CommitLineData
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
3// $Id: acquire-worker.cc,v 1.34 2001/05/22 04:42:54 jgg Exp $
4/* ######################################################################
5
6 Acquire Worker
7
8 The worker process can startup either as a Configuration prober
9 or as a queue runner. As a configuration prober it only reads the
10 configuration message and
11
12 ##################################################################### */
13 /*}}}*/
14// Include Files /*{{{*/
15#include <config.h>
16
17#include <apt-pkg/acquire.h>
18#include <apt-pkg/acquire-worker.h>
19#include <apt-pkg/acquire-item.h>
20#include <apt-pkg/configuration.h>
21#include <apt-pkg/error.h>
22#include <apt-pkg/fileutl.h>
23#include <apt-pkg/strutl.h>
24#include <apt-pkg/hashes.h>
25
26#include <string>
27#include <vector>
28#include <iostream>
29#include <sstream>
30
31#include <sys/stat.h>
32#include <stdlib.h>
33#include <unistd.h>
34#include <signal.h>
35#include <stdio.h>
36#include <errno.h>
37#include <sys/types.h>
38#include <pwd.h>
39#include <grp.h>
40
41#include <apti18n.h>
42 /*}}}*/
43
44using namespace std;
45
46// Worker::Worker - Constructor for Queue startup /*{{{*/
47// ---------------------------------------------------------------------
48/* */
49pkgAcquire::Worker::Worker(Queue *Q,MethodConfig *Cnf,
50 pkgAcquireStatus *Log) : Log(Log)
51{
52 OwnerQ = Q;
53 Config = Cnf;
54 Access = Cnf->Access;
55 CurrentItem = 0;
56 TotalSize = 0;
57 CurrentSize = 0;
58
59 Construct();
60}
61 /*}}}*/
62// Worker::Worker - Constructor for method config startup /*{{{*/
63// ---------------------------------------------------------------------
64/* */
65pkgAcquire::Worker::Worker(MethodConfig *Cnf)
66{
67 OwnerQ = 0;
68 Config = Cnf;
69 Access = Cnf->Access;
70 CurrentItem = 0;
71 TotalSize = 0;
72 CurrentSize = 0;
73
74 Construct();
75}
76 /*}}}*/
77// Worker::Construct - Constructor helper /*{{{*/
78// ---------------------------------------------------------------------
79/* */
80void pkgAcquire::Worker::Construct()
81{
82 NextQueue = 0;
83 NextAcquire = 0;
84 Process = -1;
85 InFd = -1;
86 OutFd = -1;
87 OutReady = false;
88 InReady = false;
89 Debug = _config->FindB("Debug::pkgAcquire::Worker",false);
90}
91 /*}}}*/
92// Worker::~Worker - Destructor /*{{{*/
93// ---------------------------------------------------------------------
94/* */
95pkgAcquire::Worker::~Worker()
96{
97 close(InFd);
98 close(OutFd);
99
100 if (Process > 0)
101 {
102 /* Closing of stdin is the signal to exit and die when the process
103 indicates it needs cleanup */
104 if (Config->NeedsCleanup == false)
105 kill(Process,SIGINT);
106 ExecWait(Process,Access.c_str(),true);
107 }
108}
109 /*}}}*/
110// Worker::Start - Start the worker process /*{{{*/
111// ---------------------------------------------------------------------
112/* This forks the method and inits the communication channel */
113bool pkgAcquire::Worker::Start()
114{
115 // Get the method path
116 string Method = _config->FindDir("Dir::Bin::Methods") + Access;
117 if (FileExists(Method) == false)
118 {
119 _error->Error(_("The method driver %s could not be found."),Method.c_str());
120 if (Access == "https")
121 _error->Notice(_("Is the package %s installed?"), "apt-transport-https");
122 return false;
123 }
124
125 if (Debug == true)
126 clog << "Starting method '" << Method << '\'' << endl;
127
128 // Create the pipes
129 int Pipes[4] = {-1,-1,-1,-1};
130 if (pipe(Pipes) != 0 || pipe(Pipes+2) != 0)
131 {
132 _error->Errno("pipe","Failed to create IPC pipe to subprocess");
133 for (int I = 0; I != 4; I++)
134 close(Pipes[I]);
135 return false;
136 }
137 for (int I = 0; I != 4; I++)
138 SetCloseExec(Pipes[I],true);
139
140 // Fork off the process
141 Process = ExecFork();
142 if (Process == 0)
143 {
144 // Setup the FDs
145 dup2(Pipes[1],STDOUT_FILENO);
146 dup2(Pipes[2],STDIN_FILENO);
147 SetCloseExec(STDOUT_FILENO,false);
148 SetCloseExec(STDIN_FILENO,false);
149 SetCloseExec(STDERR_FILENO,false);
150
151 const char *Args[2];
152 Args[0] = Method.c_str();
153 Args[1] = 0;
154 execv(Args[0],(char **)Args);
155 cerr << "Failed to exec method " << Args[0] << endl;
156 _exit(100);
157 }
158
159 // Fix up our FDs
160 InFd = Pipes[0];
161 OutFd = Pipes[3];
162 SetNonBlock(Pipes[0],true);
163 SetNonBlock(Pipes[3],true);
164 close(Pipes[1]);
165 close(Pipes[2]);
166 OutReady = false;
167 InReady = true;
168
169 // Read the configuration data
170 if (WaitFd(InFd) == false ||
171 ReadMessages() == false)
172 return _error->Error(_("Method %s did not start correctly"),Method.c_str());
173
174 RunMessages();
175 if (OwnerQ != 0)
176 SendConfiguration();
177
178 return true;
179}
180 /*}}}*/
181// Worker::ReadMessages - Read all pending messages into the list /*{{{*/
182// ---------------------------------------------------------------------
183/* */
184bool pkgAcquire::Worker::ReadMessages()
185{
186 if (::ReadMessages(InFd,MessageQueue) == false)
187 return MethodFailure();
188 return true;
189}
190 /*}}}*/
191// Worker::RunMessage - Empty the message queue /*{{{*/
192// ---------------------------------------------------------------------
193/* This takes the messages from the message queue and runs them through
194 the parsers in order. */
195bool pkgAcquire::Worker::RunMessages()
196{
197 while (MessageQueue.empty() == false)
198 {
199 string Message = MessageQueue.front();
200 MessageQueue.erase(MessageQueue.begin());
201
202 if (Debug == true)
203 clog << " <- " << Access << ':' << QuoteString(Message,"\n") << endl;
204
205 // Fetch the message number
206 char *End;
207 int Number = strtol(Message.c_str(),&End,10);
208 if (End == Message.c_str())
209 return _error->Error("Invalid message from method %s: %s",Access.c_str(),Message.c_str());
210
211 string URI = LookupTag(Message,"URI");
212 pkgAcquire::Queue::QItem *Itm = 0;
213 if (URI.empty() == false)
214 Itm = OwnerQ->FindItem(URI,this);
215
216 // update used mirror
217 string UsedMirror = LookupTag(Message,"UsedMirror", "");
218 if (!UsedMirror.empty() &&
219 Itm &&
220 Itm->Description.find(" ") != string::npos)
221 {
222 Itm->Description.replace(0, Itm->Description.find(" "), UsedMirror);
223 // FIXME: will we need this as well?
224 //Itm->ShortDesc = UsedMirror;
225 }
226
227 // Determine the message number and dispatch
228 switch (Number)
229 {
230 // 100 Capabilities
231 case 100:
232 if (Capabilities(Message) == false)
233 return _error->Error("Unable to process Capabilities message from %s",Access.c_str());
234 break;
235
236 // 101 Log
237 case 101:
238 if (Debug == true)
239 clog << " <- (log) " << LookupTag(Message,"Message") << endl;
240 break;
241
242 // 102 Status
243 case 102:
244 Status = LookupTag(Message,"Message");
245 break;
246
247 // 103 Redirect
248 case 103:
249 {
250 if (Itm == 0)
251 {
252 _error->Error("Method gave invalid 103 Redirect message");
253 break;
254 }
255
256 string NewURI = LookupTag(Message,"New-URI",URI.c_str());
257 Itm->URI = NewURI;
258
259 ItemDone();
260
261 // Change the status so that it can be dequeued
262 for (pkgAcquire::Queue::QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O)
263 {
264 pkgAcquire::Item *Owner = *O;
265 Owner->Status = pkgAcquire::Item::StatIdle;
266 }
267 // Mark the item as done (taking care of all queues)
268 // and then put it in the main queue again
269 std::vector<Item*> const ItmOwners = Itm->Owners;
270 OwnerQ->ItemDone(Itm);
271 Itm = NULL;
272 for (pkgAcquire::Queue::QItem::owner_iterator O = ItmOwners.begin(); O != ItmOwners.end(); ++O)
273 {
274 pkgAcquire::Item *Owner = *O;
275 pkgAcquire::ItemDesc desc = Owner->GetItemDesc();
276 desc.URI = NewURI;
277 OwnerQ->Owner->Enqueue(desc);
278
279 if (Log != 0)
280 Log->Done(Owner->GetItemDesc());
281 }
282 break;
283 }
284
285 // 200 URI Start
286 case 200:
287 {
288 if (Itm == 0)
289 {
290 _error->Error("Method gave invalid 200 URI Start message");
291 break;
292 }
293
294 CurrentItem = Itm;
295 CurrentSize = 0;
296 TotalSize = strtoull(LookupTag(Message,"Size","0").c_str(), NULL, 10);
297 ResumePoint = strtoull(LookupTag(Message,"Resume-Point","0").c_str(), NULL, 10);
298 for (pkgAcquire::Queue::QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O)
299 {
300 (*O)->Start(Message, TotalSize);
301
302 // Display update before completion
303 if (Log != 0)
304 {
305 if (Log->MorePulses == true)
306 Log->Pulse((*O)->GetOwner());
307 Log->Fetch((*O)->GetItemDesc());
308 }
309 }
310
311 break;
312 }
313
314 // 201 URI Done
315 case 201:
316 {
317 if (Itm == 0)
318 {
319 _error->Error("Method gave invalid 201 URI Done message");
320 break;
321 }
322
323 PrepareFiles("201::URIDone", Itm);
324
325 // Display update before completion
326 if (Log != 0 && Log->MorePulses == true)
327 for (pkgAcquire::Queue::QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O)
328 Log->Pulse((*O)->GetOwner());
329
330 std::string const filename = LookupTag(Message, "Filename", Itm->Owner->DestFile.c_str());
331 HashStringList ReceivedHashes;
332 {
333 // see if we got hashes to verify
334 for (char const * const * type = HashString::SupportedHashes(); *type != NULL; ++type)
335 {
336 std::string const tagname = std::string(*type) + "-Hash";
337 std::string const hashsum = LookupTag(Message, tagname.c_str());
338 if (hashsum.empty() == false)
339 ReceivedHashes.push_back(HashString(*type, hashsum));
340 }
341 // not all methods always sent Hashes our way
342 if (ReceivedHashes.usable() == false)
343 {
344 HashStringList const ExpectedHashes = Itm->GetExpectedHashes();
345 if (ExpectedHashes.usable() == true && RealFileExists(filename))
346 {
347 Hashes calc(ExpectedHashes);
348 FileFd file(filename, FileFd::ReadOnly, FileFd::None);
349 calc.AddFD(file);
350 ReceivedHashes = calc.GetHashStringList();
351 }
352 }
353 }
354
355 // only local files can refer other filenames and counting them as fetched would be unfair
356 if (Log != NULL && filename != Itm->Owner->DestFile)
357 Log->Fetched(ReceivedHashes.FileSize(),atoi(LookupTag(Message,"Resume-Point","0").c_str()));
358
359 std::vector<Item*> const ItmOwners = Itm->Owners;
360 OwnerQ->ItemDone(Itm);
361 Itm = NULL;
362
363 bool const isIMSHit = StringToBool(LookupTag(Message,"IMS-Hit"),false) ||
364 StringToBool(LookupTag(Message,"Alt-IMS-Hit"),false);
365
366 for (pkgAcquire::Queue::QItem::owner_iterator O = ItmOwners.begin(); O != ItmOwners.end(); ++O)
367 {
368 pkgAcquire::Item * const Owner = *O;
369 HashStringList const ExpectedHashes = Owner->GetExpectedHashes();
370 if(_config->FindB("Debug::pkgAcquire::Auth", false) == true)
371 {
372 std::clog << "201 URI Done: " << Owner->DescURI() << endl
373 << "ReceivedHash:" << endl;
374 for (HashStringList::const_iterator hs = ReceivedHashes.begin(); hs != ReceivedHashes.end(); ++hs)
375 std::clog << "\t- " << hs->toStr() << std::endl;
376 std::clog << "ExpectedHash:" << endl;
377 for (HashStringList::const_iterator hs = ExpectedHashes.begin(); hs != ExpectedHashes.end(); ++hs)
378 std::clog << "\t- " << hs->toStr() << std::endl;
379 std::clog << endl;
380 }
381
382 // decide if what we got is what we expected
383 bool consideredOkay = false;
384 if (ExpectedHashes.usable())
385 {
386 if (ReceivedHashes.usable() == false)
387 {
388 /* IMS-Hits can't be checked here as we will have uncompressed file,
389 but the hashes for the compressed file. What we have was good through
390 so all we have to ensure later is that we are not stalled. */
391 consideredOkay = isIMSHit;
392 }
393 else if (ReceivedHashes == ExpectedHashes)
394 consideredOkay = true;
395 else
396 consideredOkay = false;
397
398 }
399 else if (Owner->HashesRequired() == true)
400 consideredOkay = false;
401 else
402 consideredOkay = true;
403
404 if (consideredOkay == true)
405 {
406 Owner->Done(Message, ReceivedHashes, Config);
407
408 // Log that we are done
409 if (Log != 0)
410 {
411 if (isIMSHit)
412 Log->IMSHit(Owner->GetItemDesc());
413 else
414 Log->Done(Owner->GetItemDesc());
415 }
416 }
417 else
418 {
419 Owner->Status = pkgAcquire::Item::StatAuthError;
420 Owner->Failed(Message,Config);
421
422 if (Log != 0)
423 Log->Fail(Owner->GetItemDesc());
424 }
425 }
426 ItemDone();
427 break;
428 }
429
430 // 400 URI Failure
431 case 400:
432 {
433 if (Itm == 0)
434 {
435 std::string const msg = LookupTag(Message,"Message");
436 _error->Error("Method gave invalid 400 URI Failure message: %s", msg.c_str());
437 break;
438 }
439
440 PrepareFiles("400::URIFailure", Itm);
441
442 // Display update before completion
443 if (Log != 0 && Log->MorePulses == true)
444 for (pkgAcquire::Queue::QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O)
445 Log->Pulse((*O)->GetOwner());
446
447 std::vector<Item*> const ItmOwners = Itm->Owners;
448 OwnerQ->ItemDone(Itm);
449 Itm = NULL;
450
451 for (pkgAcquire::Queue::QItem::owner_iterator O = ItmOwners.begin(); O != ItmOwners.end(); ++O)
452 {
453 // set some status
454 if(LookupTag(Message,"FailReason") == "Timeout" ||
455 LookupTag(Message,"FailReason") == "TmpResolveFailure" ||
456 LookupTag(Message,"FailReason") == "ResolveFailure" ||
457 LookupTag(Message,"FailReason") == "ConnectionRefused")
458 (*O)->Status = pkgAcquire::Item::StatTransientNetworkError;
459
460 (*O)->Failed(Message,Config);
461
462 if (Log != 0)
463 Log->Fail((*O)->GetItemDesc());
464 }
465 ItemDone();
466
467 break;
468 }
469
470 // 401 General Failure
471 case 401:
472 _error->Error("Method %s General failure: %s",Access.c_str(),LookupTag(Message,"Message").c_str());
473 break;
474
475 // 403 Media Change
476 case 403:
477 MediaChange(Message);
478 break;
479 }
480 }
481 return true;
482}
483 /*}}}*/
484// Worker::Capabilities - 100 Capabilities handler /*{{{*/
485// ---------------------------------------------------------------------
486/* This parses the capabilities message and dumps it into the configuration
487 structure. */
488bool pkgAcquire::Worker::Capabilities(string Message)
489{
490 if (Config == 0)
491 return true;
492
493 Config->Version = LookupTag(Message,"Version");
494 Config->SingleInstance = StringToBool(LookupTag(Message,"Single-Instance"),false);
495 Config->Pipeline = StringToBool(LookupTag(Message,"Pipeline"),false);
496 Config->SendConfig = StringToBool(LookupTag(Message,"Send-Config"),false);
497 Config->LocalOnly = StringToBool(LookupTag(Message,"Local-Only"),false);
498 Config->NeedsCleanup = StringToBool(LookupTag(Message,"Needs-Cleanup"),false);
499 Config->Removable = StringToBool(LookupTag(Message,"Removable"),false);
500
501 // Some debug text
502 if (Debug == true)
503 {
504 clog << "Configured access method " << Config->Access << endl;
505 clog << "Version:" << Config->Version <<
506 " SingleInstance:" << Config->SingleInstance <<
507 " Pipeline:" << Config->Pipeline <<
508 " SendConfig:" << Config->SendConfig <<
509 " LocalOnly: " << Config->LocalOnly <<
510 " NeedsCleanup: " << Config->NeedsCleanup <<
511 " Removable: " << Config->Removable << endl;
512 }
513
514 return true;
515}
516 /*}}}*/
517// Worker::MediaChange - Request a media change /*{{{*/
518// ---------------------------------------------------------------------
519/* */
520bool pkgAcquire::Worker::MediaChange(string Message)
521{
522 int status_fd = _config->FindI("APT::Status-Fd",-1);
523 if(status_fd > 0)
524 {
525 string Media = LookupTag(Message,"Media");
526 string Drive = LookupTag(Message,"Drive");
527 ostringstream msg,status;
528 ioprintf(msg,_("Please insert the disc labeled: "
529 "'%s' "
530 "in the drive '%s' and press enter."),
531 Media.c_str(),Drive.c_str());
532 status << "media-change: " // message
533 << Media << ":" // media
534 << Drive << ":" // drive
535 << msg.str() // l10n message
536 << endl;
537
538 std::string const dlstatus = status.str();
539 FileFd::Write(status_fd, dlstatus.c_str(), dlstatus.size());
540 }
541
542 if (Log == 0 || Log->MediaChange(LookupTag(Message,"Media"),
543 LookupTag(Message,"Drive")) == false)
544 {
545 char S[300];
546 snprintf(S,sizeof(S),"603 Media Changed\nFailed: true\n\n");
547 if (Debug == true)
548 clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl;
549 OutQueue += S;
550 OutReady = true;
551 return true;
552 }
553
554 char S[300];
555 snprintf(S,sizeof(S),"603 Media Changed\n\n");
556 if (Debug == true)
557 clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl;
558 OutQueue += S;
559 OutReady = true;
560 return true;
561}
562 /*}}}*/
563// Worker::SendConfiguration - Send the config to the method /*{{{*/
564// ---------------------------------------------------------------------
565/* */
566bool pkgAcquire::Worker::SendConfiguration()
567{
568 if (Config->SendConfig == false)
569 return true;
570
571 if (OutFd == -1)
572 return false;
573
574 /* Write out all of the configuration directives by walking the
575 configuration tree */
576 std::ostringstream Message;
577 Message << "601 Configuration\n";
578 _config->Dump(Message, NULL, "Config-Item: %F=%V\n", false);
579 Message << '\n';
580
581 if (Debug == true)
582 clog << " -> " << Access << ':' << QuoteString(Message.str(),"\n") << endl;
583 OutQueue += Message.str();
584 OutReady = true;
585
586 return true;
587}
588 /*}}}*/
589// Worker::QueueItem - Add an item to the outbound queue /*{{{*/
590// ---------------------------------------------------------------------
591/* Send a URI Acquire message to the method */
592bool pkgAcquire::Worker::QueueItem(pkgAcquire::Queue::QItem *Item)
593{
594 if (OutFd == -1)
595 return false;
596
597 string Message = "600 URI Acquire\n";
598 Message.reserve(300);
599 Message += "URI: " + Item->URI;
600 Message += "\nFilename: " + Item->Owner->DestFile;
601
602 HashStringList const hsl = Item->GetExpectedHashes();
603 for (HashStringList::const_iterator hs = hsl.begin(); hs != hsl.end(); ++hs)
604 Message += "\nExpected-" + hs->HashType() + ": " + hs->HashValue();
605
606 if (hsl.FileSize() == 0)
607 {
608 unsigned long long FileSize = Item->GetMaximumSize();
609 if(FileSize > 0)
610 {
611 string MaximumSize;
612 strprintf(MaximumSize, "%llu", FileSize);
613 Message += "\nMaximum-Size: " + MaximumSize;
614 }
615 }
616
617 Item->SyncDestinationFiles();
618 Message += Item->Custom600Headers();
619 Message += "\n\n";
620
621 if (RealFileExists(Item->Owner->DestFile))
622 {
623 std::string SandboxUser = _config->Find("APT::Sandbox::User");
624 ChangeOwnerAndPermissionOfFile("Item::QueueURI", Item->Owner->DestFile.c_str(),
625 SandboxUser.c_str(), "root", 0600);
626 }
627
628 if (Debug == true)
629 clog << " -> " << Access << ':' << QuoteString(Message,"\n") << endl;
630 OutQueue += Message;
631 OutReady = true;
632
633 return true;
634}
635 /*}}}*/
636// Worker::OutFdRead - Out bound FD is ready /*{{{*/
637// ---------------------------------------------------------------------
638/* */
639bool pkgAcquire::Worker::OutFdReady()
640{
641 int Res;
642 do
643 {
644 Res = write(OutFd,OutQueue.c_str(),OutQueue.length());
645 }
646 while (Res < 0 && errno == EINTR);
647
648 if (Res <= 0)
649 return MethodFailure();
650
651 OutQueue.erase(0,Res);
652 if (OutQueue.empty() == true)
653 OutReady = false;
654
655 return true;
656}
657 /*}}}*/
658// Worker::InFdRead - In bound FD is ready /*{{{*/
659// ---------------------------------------------------------------------
660/* */
661bool pkgAcquire::Worker::InFdReady()
662{
663 if (ReadMessages() == false)
664 return false;
665 RunMessages();
666 return true;
667}
668 /*}}}*/
669// Worker::MethodFailure - Called when the method fails /*{{{*/
670// ---------------------------------------------------------------------
671/* This is called when the method is believed to have failed, probably because
672 read returned -1. */
673bool pkgAcquire::Worker::MethodFailure()
674{
675 _error->Error("Method %s has died unexpectedly!",Access.c_str());
676
677 // do not reap the child here to show meaningfull error to the user
678 ExecWait(Process,Access.c_str(),false);
679 Process = -1;
680 close(InFd);
681 close(OutFd);
682 InFd = -1;
683 OutFd = -1;
684 OutReady = false;
685 InReady = false;
686 OutQueue = string();
687 MessageQueue.erase(MessageQueue.begin(),MessageQueue.end());
688
689 return false;
690}
691 /*}}}*/
692// Worker::Pulse - Called periodically /*{{{*/
693// ---------------------------------------------------------------------
694/* */
695void pkgAcquire::Worker::Pulse()
696{
697 if (CurrentItem == 0)
698 return;
699
700 struct stat Buf;
701 if (stat(CurrentItem->Owner->DestFile.c_str(),&Buf) != 0)
702 return;
703 CurrentSize = Buf.st_size;
704}
705 /*}}}*/
706// Worker::ItemDone - Called when the current item is finished /*{{{*/
707// ---------------------------------------------------------------------
708/* */
709void pkgAcquire::Worker::ItemDone()
710{
711 CurrentItem = 0;
712 CurrentSize = 0;
713 TotalSize = 0;
714 Status = string();
715}
716 /*}}}*/
717void pkgAcquire::Worker::PrepareFiles(char const * const caller, pkgAcquire::Queue::QItem const * const Itm)/*{{{*/
718{
719 if (RealFileExists(Itm->Owner->DestFile))
720 {
721 ChangeOwnerAndPermissionOfFile(caller, Itm->Owner->DestFile.c_str(), "root", "root", 0644);
722 std::string const filename = Itm->Owner->DestFile;
723 for (pkgAcquire::Queue::QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O)
724 {
725 pkgAcquire::Item const * const Owner = *O;
726 if (Owner->DestFile == filename)
727 continue;
728 unlink(Owner->DestFile.c_str());
729 if (link(filename.c_str(), Owner->DestFile.c_str()) != 0)
730 {
731 // diferent mounts can't happen for us as we download to lists/ by default,
732 // but if the system is reused by others the locations can potentially be on
733 // different disks, so use symlink as poor-men replacement.
734 // FIXME: Real copying as last fallback, but that is costly, so offload to a method preferable
735 if (symlink(filename.c_str(), Owner->DestFile.c_str()) != 0)
736 _error->Error("Can't create (sym)link of file %s to %s", filename.c_str(), Owner->DestFile.c_str());
737 }
738 }
739 }
740 else
741 {
742 for (pkgAcquire::Queue::QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O)
743 unlink((*O)->DestFile.c_str());
744 }
745}
746 /*}}}*/