1 // -*- mode: cpp; mode: fold -*-
3 // $Id: acquire-worker.cc,v 1.34 2001/05/22 04:42:54 jgg Exp $
4 /* ######################################################################
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
12 ##################################################################### */
14 // Include Files /*{{{*/
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>
44 // Worker::Worker - Constructor for Queue startup /*{{{*/
45 pkgAcquire::Worker::Worker(Queue
*Q
, MethodConfig
*Cnf
, pkgAcquireStatus
*log
) :
46 d(NULL
), OwnerQ(Q
), Log(log
), Config(Cnf
), Access(Cnf
->Access
),
47 CurrentItem(nullptr), CurrentSize(0), TotalSize(0)
52 // Worker::Worker - Constructor for method config startup /*{{{*/
53 pkgAcquire::Worker::Worker(MethodConfig
*Cnf
) : Worker(nullptr, Cnf
, nullptr)
57 // Worker::Construct - Constructor helper /*{{{*/
58 // ---------------------------------------------------------------------
60 void pkgAcquire::Worker::Construct()
69 Debug
= _config
->FindB("Debug::pkgAcquire::Worker",false);
72 // Worker::~Worker - Destructor /*{{{*/
73 // ---------------------------------------------------------------------
75 pkgAcquire::Worker::~Worker()
82 /* Closing of stdin is the signal to exit and die when the process
83 indicates it needs cleanup */
84 if (Config
->NeedsCleanup
== false)
86 ExecWait(Process
,Access
.c_str(),true);
90 // Worker::Start - Start the worker process /*{{{*/
91 // ---------------------------------------------------------------------
92 /* This forks the method and inits the communication channel */
93 bool pkgAcquire::Worker::Start()
95 // Get the method path
96 string Method
= _config
->FindDir("Dir::Bin::Methods") + Access
;
97 if (FileExists(Method
) == false)
99 _error
->Error(_("The method driver %s could not be found."),Method
.c_str());
100 std::string
const A(Access
.cbegin(), std::find(Access
.cbegin(), Access
.cend(), '+'));
102 strprintf(pkg
, "apt-transport-%s", A
.c_str());
103 _error
->Notice(_("Is the package %s installed?"), pkg
.c_str());
108 clog
<< "Starting method '" << Method
<< '\'' << endl
;
111 int Pipes
[4] = {-1,-1,-1,-1};
112 if (pipe(Pipes
) != 0 || pipe(Pipes
+2) != 0)
114 _error
->Errno("pipe","Failed to create IPC pipe to subprocess");
115 for (int I
= 0; I
!= 4; I
++)
119 for (int I
= 0; I
!= 4; I
++)
120 SetCloseExec(Pipes
[I
],true);
122 // Fork off the process
123 Process
= ExecFork();
127 dup2(Pipes
[1],STDOUT_FILENO
);
128 dup2(Pipes
[2],STDIN_FILENO
);
129 SetCloseExec(STDOUT_FILENO
,false);
130 SetCloseExec(STDIN_FILENO
,false);
131 SetCloseExec(STDERR_FILENO
,false);
134 Args
[0] = Method
.c_str();
136 execv(Args
[0],(char **)Args
);
137 cerr
<< "Failed to exec method " << Args
[0] << endl
;
144 SetNonBlock(Pipes
[0],true);
145 SetNonBlock(Pipes
[3],true);
151 // Read the configuration data
152 if (WaitFd(InFd
) == false ||
153 ReadMessages() == false)
154 return _error
->Error(_("Method %s did not start correctly"),Method
.c_str());
163 // Worker::ReadMessages - Read all pending messages into the list /*{{{*/
164 // ---------------------------------------------------------------------
166 bool pkgAcquire::Worker::ReadMessages()
168 if (::ReadMessages(InFd
,MessageQueue
) == false)
169 return MethodFailure();
173 // Worker::RunMessage - Empty the message queue /*{{{*/
174 // ---------------------------------------------------------------------
175 /* This takes the messages from the message queue and runs them through
176 the parsers in order. */
177 enum class APT_HIDDEN MessageType
{
186 GENERAL_FAILURE
= 401,
189 static bool isDoomedItem(pkgAcquire::Item
const * const Itm
)
191 auto const TransItm
= dynamic_cast<pkgAcqTransactionItem
const * const>(Itm
);
192 if (TransItm
== nullptr)
194 return TransItm
->TransactionManager
->State
!= pkgAcqTransactionItem::TransactionStarted
;
196 bool pkgAcquire::Worker::RunMessages()
198 while (MessageQueue
.empty() == false)
200 string Message
= MessageQueue
.front();
201 MessageQueue
.erase(MessageQueue
.begin());
204 clog
<< " <- " << Access
<< ':' << QuoteString(Message
,"\n") << endl
;
206 // Fetch the message number
208 MessageType
const Number
= static_cast<MessageType
>(strtoul(Message
.c_str(),&End
,10));
209 if (End
== Message
.c_str())
210 return _error
->Error("Invalid message from method %s: %s",Access
.c_str(),Message
.c_str());
212 string URI
= LookupTag(Message
,"URI");
213 pkgAcquire::Queue::QItem
*Itm
= NULL
;
214 if (URI
.empty() == false)
215 Itm
= OwnerQ
->FindItem(URI
,this);
219 // update used mirror
220 string UsedMirror
= LookupTag(Message
,"UsedMirror", "");
221 if (UsedMirror
.empty() == false)
223 for (pkgAcquire::Queue::QItem::owner_iterator O
= Itm
->Owners
.begin(); O
!= Itm
->Owners
.end(); ++O
)
224 (*O
)->UsedMirror
= UsedMirror
;
226 if (Itm
->Description
.find(" ") != string::npos
)
227 Itm
->Description
.replace(0, Itm
->Description
.find(" "), UsedMirror
);
231 // Determine the message number and dispatch
234 case MessageType::CAPABILITIES
:
235 if (Capabilities(Message
) == false)
236 return _error
->Error("Unable to process Capabilities message from %s",Access
.c_str());
239 case MessageType::LOG
:
241 clog
<< " <- (log) " << LookupTag(Message
,"Message") << endl
;
244 case MessageType::STATUS
:
245 Status
= LookupTag(Message
,"Message");
248 case MessageType::REDIRECT
:
252 _error
->Error("Method gave invalid 103 Redirect message");
256 std::string
const NewURI
= LookupTag(Message
,"New-URI",URI
.c_str());
261 // Change the status so that it can be dequeued
262 for (auto const &O
: Itm
->Owners
)
263 O
->Status
= pkgAcquire::Item::StatIdle
;
264 // Mark the item as done (taking care of all queues)
265 // and then put it in the main queue again
266 std::vector
<Item
*> const ItmOwners
= Itm
->Owners
;
267 OwnerQ
->ItemDone(Itm
);
269 for (auto const &Owner
: ItmOwners
)
271 pkgAcquire::ItemDesc
&desc
= Owner
->GetItemDesc();
272 if (Owner
->IsRedirectionLoop(NewURI
))
274 std::string msg
= Message
;
275 msg
.append("\nFailReason: RedirectionLoop");
276 Owner
->Failed(msg
, Config
);
278 Log
->Fail(Owner
->GetItemDesc());
285 // if we change site, treat it as a mirror change
286 if (URI::SiteOnly(NewURI
) != URI::SiteOnly(desc
.URI
))
288 auto const firstSpace
= desc
.Description
.find(" ");
289 if (firstSpace
!= std::string::npos
)
291 std::string
const OldSite
= desc
.Description
.substr(0, firstSpace
);
292 if (likely(APT::String::Startswith(desc
.URI
, OldSite
)))
294 std::string
const OldExtra
= desc
.URI
.substr(OldSite
.length() + 1);
295 if (likely(APT::String::Endswith(NewURI
, OldExtra
)))
297 std::string
const NewSite
= NewURI
.substr(0, NewURI
.length() - OldExtra
.length());
298 Owner
->UsedMirror
= URI::ArchiveOnly(NewSite
);
299 desc
.Description
.replace(0, firstSpace
, Owner
->UsedMirror
);
305 if (isDoomedItem(Owner
) == false)
306 OwnerQ
->Owner
->Enqueue(desc
);
311 case MessageType::WARNING
:
312 _error
->Warning("%s: %s", Itm
->Owner
->DescURI().c_str(), LookupTag(Message
,"Message").c_str());
315 case MessageType::URI_START
:
319 _error
->Error("Method gave invalid 200 URI Start message");
325 TotalSize
= strtoull(LookupTag(Message
,"Size","0").c_str(), NULL
, 10);
326 ResumePoint
= strtoull(LookupTag(Message
,"Resume-Point","0").c_str(), NULL
, 10);
327 for (auto const Owner
: Itm
->Owners
)
329 Owner
->Start(Message
, TotalSize
);
330 // Display update before completion
333 if (Log
->MorePulses
== true)
334 Log
->Pulse(Owner
->GetOwner());
335 Log
->Fetch(Owner
->GetItemDesc());
342 case MessageType::URI_DONE
:
346 _error
->Error("Method gave invalid 201 URI Done message");
350 PrepareFiles("201::URIDone", Itm
);
352 // Display update before completion
353 if (Log
!= 0 && Log
->MorePulses
== true)
354 for (pkgAcquire::Queue::QItem::owner_iterator O
= Itm
->Owners
.begin(); O
!= Itm
->Owners
.end(); ++O
)
355 Log
->Pulse((*O
)->GetOwner());
357 HashStringList ReceivedHashes
;
359 std::string
const givenfilename
= LookupTag(Message
, "Filename");
360 std::string
const filename
= givenfilename
.empty() ? Itm
->Owner
->DestFile
: givenfilename
;
361 // see if we got hashes to verify
362 for (char const * const * type
= HashString::SupportedHashes(); *type
!= NULL
; ++type
)
364 std::string
const tagname
= std::string(*type
) + "-Hash";
365 std::string
const hashsum
= LookupTag(Message
, tagname
.c_str());
366 if (hashsum
.empty() == false)
367 ReceivedHashes
.push_back(HashString(*type
, hashsum
));
369 // not all methods always sent Hashes our way
370 if (ReceivedHashes
.usable() == false)
372 HashStringList
const ExpectedHashes
= Itm
->GetExpectedHashes();
373 if (ExpectedHashes
.usable() == true && RealFileExists(filename
))
375 Hashes
calc(ExpectedHashes
);
376 FileFd
file(filename
, FileFd::ReadOnly
, FileFd::None
);
378 ReceivedHashes
= calc
.GetHashStringList();
382 // only local files can refer other filenames and counting them as fetched would be unfair
383 if (Log
!= NULL
&& Itm
->Owner
->Complete
== false && Itm
->Owner
->Local
== false && givenfilename
== filename
)
384 Log
->Fetched(ReceivedHashes
.FileSize(),atoi(LookupTag(Message
,"Resume-Point","0").c_str()));
387 std::vector
<Item
*> const ItmOwners
= Itm
->Owners
;
388 OwnerQ
->ItemDone(Itm
);
391 bool const isIMSHit
= StringToBool(LookupTag(Message
,"IMS-Hit"),false) ||
392 StringToBool(LookupTag(Message
,"Alt-IMS-Hit"),false);
393 auto const forcedHash
= _config
->Find("Acquire::ForceHash");
394 for (auto const Owner
: ItmOwners
)
396 HashStringList
const ExpectedHashes
= Owner
->GetExpectedHashes();
397 if(_config
->FindB("Debug::pkgAcquire::Auth", false) == true)
399 std::clog
<< "201 URI Done: " << Owner
->DescURI() << endl
400 << "ReceivedHash:" << endl
;
401 for (HashStringList::const_iterator hs
= ReceivedHashes
.begin(); hs
!= ReceivedHashes
.end(); ++hs
)
402 std::clog
<< "\t- " << hs
->toStr() << std::endl
;
403 std::clog
<< "ExpectedHash:" << endl
;
404 for (HashStringList::const_iterator hs
= ExpectedHashes
.begin(); hs
!= ExpectedHashes
.end(); ++hs
)
405 std::clog
<< "\t- " << hs
->toStr() << std::endl
;
409 // decide if what we got is what we expected
410 bool consideredOkay
= false;
411 if ((forcedHash
.empty() && ExpectedHashes
.empty() == false) ||
412 (forcedHash
.empty() == false && ExpectedHashes
.usable()))
414 if (ReceivedHashes
.empty())
416 /* IMS-Hits can't be checked here as we will have uncompressed file,
417 but the hashes for the compressed file. What we have was good through
418 so all we have to ensure later is that we are not stalled. */
419 consideredOkay
= isIMSHit
;
421 else if (ReceivedHashes
== ExpectedHashes
)
422 consideredOkay
= true;
424 consideredOkay
= false;
428 consideredOkay
= !Owner
->HashesRequired();
430 if (consideredOkay
== true)
431 consideredOkay
= Owner
->VerifyDone(Message
, Config
);
432 else // hashsum mismatch
433 Owner
->Status
= pkgAcquire::Item::StatAuthError
;
436 if (consideredOkay
== true)
438 if (isDoomedItem(Owner
) == false)
439 Owner
->Done(Message
, ReceivedHashes
, Config
);
443 Log
->IMSHit(Owner
->GetItemDesc());
445 Log
->Done(Owner
->GetItemDesc());
450 if (isDoomedItem(Owner
) == false)
452 if (Message
.find("\nFailReason:") == std::string::npos
)
454 if (ReceivedHashes
!= ExpectedHashes
)
455 Message
.append("\nFailReason: HashSumMismatch");
457 Message
.append("\nFailReason: WeakHashSums");
459 Owner
->Failed(Message
,Config
);
462 Log
->Fail(Owner
->GetItemDesc());
469 case MessageType::URI_FAILURE
:
473 std::string
const msg
= LookupTag(Message
,"Message");
474 _error
->Error("Method gave invalid 400 URI Failure message: %s", msg
.c_str());
478 PrepareFiles("400::URIFailure", Itm
);
480 // Display update before completion
481 if (Log
!= nullptr && Log
->MorePulses
== true)
482 for (pkgAcquire::Queue::QItem::owner_iterator O
= Itm
->Owners
.begin(); O
!= Itm
->Owners
.end(); ++O
)
483 Log
->Pulse((*O
)->GetOwner());
485 std::vector
<Item
*> const ItmOwners
= Itm
->Owners
;
486 OwnerQ
->ItemDone(Itm
);
489 bool errTransient
= false, errAuthErr
= false;
491 std::string
const failReason
= LookupTag(Message
, "FailReason");
493 auto const reasons
= { "Timeout", "ConnectionRefused",
494 "ConnectionTimedOut", "ResolveFailure", "TmpResolveFailure" };
495 errTransient
= std::find(std::begin(reasons
), std::end(reasons
), failReason
) != std::end(reasons
);
497 if (errTransient
== false)
499 auto const reasons
= { "HashSumMismatch", "WeakHashSums", "MaximumSizeExceeded" };
500 errAuthErr
= std::find(std::begin(reasons
), std::end(reasons
), failReason
) != std::end(reasons
);
504 for (auto const Owner
: ItmOwners
)
506 if (errAuthErr
&& Owner
->GetExpectedHashes().empty() == false)
507 Owner
->Status
= pkgAcquire::Item::StatAuthError
;
508 else if (errTransient
)
509 Owner
->Status
= pkgAcquire::Item::StatTransientNetworkError
;
511 if (isDoomedItem(Owner
) == false)
512 Owner
->Failed(Message
,Config
);
514 Log
->Fail(Owner
->GetItemDesc());
521 case MessageType::GENERAL_FAILURE
:
522 _error
->Error("Method %s General failure: %s",Access
.c_str(),LookupTag(Message
,"Message").c_str());
525 case MessageType::MEDIA_CHANGE
:
526 MediaChange(Message
);
533 // Worker::Capabilities - 100 Capabilities handler /*{{{*/
534 // ---------------------------------------------------------------------
535 /* This parses the capabilities message and dumps it into the configuration
537 bool pkgAcquire::Worker::Capabilities(string Message
)
542 Config
->Version
= LookupTag(Message
,"Version");
543 Config
->SingleInstance
= StringToBool(LookupTag(Message
,"Single-Instance"),false);
544 Config
->Pipeline
= StringToBool(LookupTag(Message
,"Pipeline"),false);
545 Config
->SendConfig
= StringToBool(LookupTag(Message
,"Send-Config"),false);
546 Config
->LocalOnly
= StringToBool(LookupTag(Message
,"Local-Only"),false);
547 Config
->NeedsCleanup
= StringToBool(LookupTag(Message
,"Needs-Cleanup"),false);
548 Config
->Removable
= StringToBool(LookupTag(Message
,"Removable"),false);
553 clog
<< "Configured access method " << Config
->Access
<< endl
;
554 clog
<< "Version:" << Config
->Version
<<
555 " SingleInstance:" << Config
->SingleInstance
<<
556 " Pipeline:" << Config
->Pipeline
<<
557 " SendConfig:" << Config
->SendConfig
<<
558 " LocalOnly: " << Config
->LocalOnly
<<
559 " NeedsCleanup: " << Config
->NeedsCleanup
<<
560 " Removable: " << Config
->Removable
<< endl
;
566 // Worker::MediaChange - Request a media change /*{{{*/
567 // ---------------------------------------------------------------------
569 bool pkgAcquire::Worker::MediaChange(string Message
)
571 int status_fd
= _config
->FindI("APT::Status-Fd",-1);
574 string Media
= LookupTag(Message
,"Media");
575 string Drive
= LookupTag(Message
,"Drive");
576 ostringstream msg
,status
;
577 ioprintf(msg
,_("Please insert the disc labeled: "
579 "in the drive '%s' and press [Enter]."),
580 Media
.c_str(),Drive
.c_str());
581 status
<< "media-change: " // message
582 << Media
<< ":" // media
583 << Drive
<< ":" // drive
584 << msg
.str() // l10n message
587 std::string
const dlstatus
= status
.str();
588 FileFd::Write(status_fd
, dlstatus
.c_str(), dlstatus
.size());
591 if (Log
== 0 || Log
->MediaChange(LookupTag(Message
,"Media"),
592 LookupTag(Message
,"Drive")) == false)
595 snprintf(S
,sizeof(S
),"603 Media Changed\nFailed: true\n\n");
597 clog
<< " -> " << Access
<< ':' << QuoteString(S
,"\n") << endl
;
604 snprintf(S
,sizeof(S
),"603 Media Changed\n\n");
606 clog
<< " -> " << Access
<< ':' << QuoteString(S
,"\n") << endl
;
612 // Worker::SendConfiguration - Send the config to the method /*{{{*/
613 // ---------------------------------------------------------------------
615 bool pkgAcquire::Worker::SendConfiguration()
617 if (Config
->SendConfig
== false)
623 /* Write out all of the configuration directives by walking the
624 configuration tree */
625 std::ostringstream Message
;
626 Message
<< "601 Configuration\n";
627 _config
->Dump(Message
, NULL
, "Config-Item: %F=%V\n", false);
631 clog
<< " -> " << Access
<< ':' << QuoteString(Message
.str(),"\n") << endl
;
632 OutQueue
+= Message
.str();
638 // Worker::QueueItem - Add an item to the outbound queue /*{{{*/
639 // ---------------------------------------------------------------------
640 /* Send a URI Acquire message to the method */
641 bool pkgAcquire::Worker::QueueItem(pkgAcquire::Queue::QItem
*Item
)
646 HashStringList
const hsl
= Item
->GetExpectedHashes();
648 if (isDoomedItem(Item
->Owner
))
651 if (hsl
.usable() == false && Item
->Owner
->HashesRequired() &&
652 _config
->Exists("Acquire::ForceHash") == false)
654 std::string
const Message
= "400 URI Failure"
655 "\nURI: " + Item
->URI
+
656 "\nFilename: " + Item
->Owner
->DestFile
+
657 "\nFailReason: WeakHashSums";
659 auto const ItmOwners
= Item
->Owners
;
660 for (auto &O
: ItmOwners
)
662 O
->Status
= pkgAcquire::Item::StatAuthError
;
663 O
->Failed(Message
, Config
);
665 Log
->Fail(O
->GetItemDesc());
667 // "queued" successfully, the item just instantly failed
671 string Message
= "600 URI Acquire\n";
672 Message
.reserve(300);
673 Message
+= "URI: " + Item
->URI
;
674 Message
+= "\nFilename: " + Item
->Owner
->DestFile
;
676 for (HashStringList::const_iterator hs
= hsl
.begin(); hs
!= hsl
.end(); ++hs
)
677 Message
+= "\nExpected-" + hs
->HashType() + ": " + hs
->HashValue();
679 if (hsl
.FileSize() == 0)
681 unsigned long long FileSize
= Item
->GetMaximumSize();
685 strprintf(MaximumSize
, "%llu", FileSize
);
686 Message
+= "\nMaximum-Size: " + MaximumSize
;
690 Item
->SyncDestinationFiles();
691 Message
+= Item
->Custom600Headers();
694 if (RealFileExists(Item
->Owner
->DestFile
))
696 std::string
const SandboxUser
= _config
->Find("APT::Sandbox::User");
697 ChangeOwnerAndPermissionOfFile("Item::QueueURI", Item
->Owner
->DestFile
.c_str(),
698 SandboxUser
.c_str(), "root", 0600);
702 clog
<< " -> " << Access
<< ':' << QuoteString(Message
,"\n") << endl
;
709 // Worker::OutFdRead - Out bound FD is ready /*{{{*/
710 // ---------------------------------------------------------------------
712 bool pkgAcquire::Worker::OutFdReady()
717 Res
= write(OutFd
,OutQueue
.c_str(),OutQueue
.length());
719 while (Res
< 0 && errno
== EINTR
);
722 return MethodFailure();
724 OutQueue
.erase(0,Res
);
725 if (OutQueue
.empty() == true)
731 // Worker::InFdRead - In bound FD is ready /*{{{*/
732 // ---------------------------------------------------------------------
734 bool pkgAcquire::Worker::InFdReady()
736 if (ReadMessages() == false)
742 // Worker::MethodFailure - Called when the method fails /*{{{*/
743 // ---------------------------------------------------------------------
744 /* This is called when the method is believed to have failed, probably because
746 bool pkgAcquire::Worker::MethodFailure()
748 _error
->Error("Method %s has died unexpectedly!",Access
.c_str());
750 // do not reap the child here to show meaningfull error to the user
751 ExecWait(Process
,Access
.c_str(),false);
760 MessageQueue
.erase(MessageQueue
.begin(),MessageQueue
.end());
765 // Worker::Pulse - Called periodically /*{{{*/
766 // ---------------------------------------------------------------------
768 void pkgAcquire::Worker::Pulse()
770 if (CurrentItem
== 0)
774 if (stat(CurrentItem
->Owner
->DestFile
.c_str(),&Buf
) != 0)
776 CurrentSize
= Buf
.st_size
;
779 // Worker::ItemDone - Called when the current item is finished /*{{{*/
780 // ---------------------------------------------------------------------
782 void pkgAcquire::Worker::ItemDone()
790 void pkgAcquire::Worker::PrepareFiles(char const * const caller
, pkgAcquire::Queue::QItem
const * const Itm
)/*{{{*/
792 if (RealFileExists(Itm
->Owner
->DestFile
))
794 ChangeOwnerAndPermissionOfFile(caller
, Itm
->Owner
->DestFile
.c_str(), "root", "root", 0644);
795 std::string
const filename
= Itm
->Owner
->DestFile
;
796 for (pkgAcquire::Queue::QItem::owner_iterator O
= Itm
->Owners
.begin(); O
!= Itm
->Owners
.end(); ++O
)
798 pkgAcquire::Item
const * const Owner
= *O
;
799 if (Owner
->DestFile
== filename
|| filename
== "/dev/null")
801 RemoveFile("PrepareFiles", Owner
->DestFile
);
802 if (link(filename
.c_str(), Owner
->DestFile
.c_str()) != 0)
804 // different mounts can't happen for us as we download to lists/ by default,
805 // but if the system is reused by others the locations can potentially be on
806 // different disks, so use symlink as poor-men replacement.
807 // FIXME: Real copying as last fallback, but that is costly, so offload to a method preferable
808 if (symlink(filename
.c_str(), Owner
->DestFile
.c_str()) != 0)
809 _error
->Error("Can't create (sym)link of file %s to %s", filename
.c_str(), Owner
->DestFile
.c_str());
815 for (pkgAcquire::Queue::QItem::owner_iterator O
= Itm
->Owners
.begin(); O
!= Itm
->Owners
.end(); ++O
)
816 RemoveFile("PrepareFiles", (*O
)->DestFile
);