]> git.saurik.com Git - apt.git/blob - apt-pkg/acquire-worker.cc
merge debian/sid into debian/experimental
[apt.git] / apt-pkg / acquire-worker.cc
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
44 using namespace std;
45
46 static void ChangeOwnerAndPermissionOfFile(char const * const requester, char const * const file, char const * const user, char const * const group, mode_t const mode) /*{{{*/
47 {
48 if (getuid() == 0 && strlen(user) != 0 && strlen(group) != 0) // if we aren't root, we can't chown, so don't try it
49 {
50 // ensure the file is owned by root and has good permissions
51 struct passwd const * const pw = getpwnam(user);
52 struct group const * const gr = getgrnam(group);
53 if (pw != NULL && gr != NULL && chown(file, pw->pw_uid, gr->gr_gid) != 0)
54 _error->WarningE(requester, "chown to %s:%s of file %s failed", user, group, file);
55 }
56 if (chmod(file, mode) != 0)
57 _error->WarningE(requester, "chmod 0%o of file %s failed", mode, file);
58 }
59 /*}}}*/
60 // Worker::Worker - Constructor for Queue startup /*{{{*/
61 // ---------------------------------------------------------------------
62 /* */
63 pkgAcquire::Worker::Worker(Queue *Q,MethodConfig *Cnf,
64 pkgAcquireStatus *Log) : Log(Log)
65 {
66 OwnerQ = Q;
67 Config = Cnf;
68 Access = Cnf->Access;
69 CurrentItem = 0;
70 TotalSize = 0;
71 CurrentSize = 0;
72
73 Construct();
74 }
75 /*}}}*/
76 // Worker::Worker - Constructor for method config startup /*{{{*/
77 // ---------------------------------------------------------------------
78 /* */
79 pkgAcquire::Worker::Worker(MethodConfig *Cnf)
80 {
81 OwnerQ = 0;
82 Config = Cnf;
83 Access = Cnf->Access;
84 CurrentItem = 0;
85 TotalSize = 0;
86 CurrentSize = 0;
87
88 Construct();
89 }
90 /*}}}*/
91 // Worker::Construct - Constructor helper /*{{{*/
92 // ---------------------------------------------------------------------
93 /* */
94 void pkgAcquire::Worker::Construct()
95 {
96 NextQueue = 0;
97 NextAcquire = 0;
98 Process = -1;
99 InFd = -1;
100 OutFd = -1;
101 OutReady = false;
102 InReady = false;
103 Debug = _config->FindB("Debug::pkgAcquire::Worker",false);
104 }
105 /*}}}*/
106 // Worker::~Worker - Destructor /*{{{*/
107 // ---------------------------------------------------------------------
108 /* */
109 pkgAcquire::Worker::~Worker()
110 {
111 close(InFd);
112 close(OutFd);
113
114 if (Process > 0)
115 {
116 /* Closing of stdin is the signal to exit and die when the process
117 indicates it needs cleanup */
118 if (Config->NeedsCleanup == false)
119 kill(Process,SIGINT);
120 ExecWait(Process,Access.c_str(),true);
121 }
122 }
123 /*}}}*/
124 // Worker::Start - Start the worker process /*{{{*/
125 // ---------------------------------------------------------------------
126 /* This forks the method and inits the communication channel */
127 bool pkgAcquire::Worker::Start()
128 {
129 // Get the method path
130 string Method = _config->FindDir("Dir::Bin::Methods") + Access;
131 if (FileExists(Method) == false)
132 {
133 _error->Error(_("The method driver %s could not be found."),Method.c_str());
134 if (Access == "https")
135 _error->Notice(_("Is the package %s installed?"), "apt-transport-https");
136 return false;
137 }
138
139 if (Debug == true)
140 clog << "Starting method '" << Method << '\'' << endl;
141
142 // Create the pipes
143 int Pipes[4] = {-1,-1,-1,-1};
144 if (pipe(Pipes) != 0 || pipe(Pipes+2) != 0)
145 {
146 _error->Errno("pipe","Failed to create IPC pipe to subprocess");
147 for (int I = 0; I != 4; I++)
148 close(Pipes[I]);
149 return false;
150 }
151 for (int I = 0; I != 4; I++)
152 SetCloseExec(Pipes[I],true);
153
154 // Fork off the process
155 Process = ExecFork();
156 if (Process == 0)
157 {
158 // Setup the FDs
159 dup2(Pipes[1],STDOUT_FILENO);
160 dup2(Pipes[2],STDIN_FILENO);
161 SetCloseExec(STDOUT_FILENO,false);
162 SetCloseExec(STDIN_FILENO,false);
163 SetCloseExec(STDERR_FILENO,false);
164
165 const char *Args[2];
166 Args[0] = Method.c_str();
167 Args[1] = 0;
168 execv(Args[0],(char **)Args);
169 cerr << "Failed to exec method " << Args[0] << endl;
170 _exit(100);
171 }
172
173 // Fix up our FDs
174 InFd = Pipes[0];
175 OutFd = Pipes[3];
176 SetNonBlock(Pipes[0],true);
177 SetNonBlock(Pipes[3],true);
178 close(Pipes[1]);
179 close(Pipes[2]);
180 OutReady = false;
181 InReady = true;
182
183 // Read the configuration data
184 if (WaitFd(InFd) == false ||
185 ReadMessages() == false)
186 return _error->Error(_("Method %s did not start correctly"),Method.c_str());
187
188 RunMessages();
189 if (OwnerQ != 0)
190 SendConfiguration();
191
192 return true;
193 }
194 /*}}}*/
195 // Worker::ReadMessages - Read all pending messages into the list /*{{{*/
196 // ---------------------------------------------------------------------
197 /* */
198 bool pkgAcquire::Worker::ReadMessages()
199 {
200 if (::ReadMessages(InFd,MessageQueue) == false)
201 return MethodFailure();
202 return true;
203 }
204 /*}}}*/
205 // Worker::RunMessage - Empty the message queue /*{{{*/
206 // ---------------------------------------------------------------------
207 /* This takes the messages from the message queue and runs them through
208 the parsers in order. */
209 bool pkgAcquire::Worker::RunMessages()
210 {
211 while (MessageQueue.empty() == false)
212 {
213 string Message = MessageQueue.front();
214 MessageQueue.erase(MessageQueue.begin());
215
216 if (Debug == true)
217 clog << " <- " << Access << ':' << QuoteString(Message,"\n") << endl;
218
219 // Fetch the message number
220 char *End;
221 int Number = strtol(Message.c_str(),&End,10);
222 if (End == Message.c_str())
223 return _error->Error("Invalid message from method %s: %s",Access.c_str(),Message.c_str());
224
225 string URI = LookupTag(Message,"URI");
226 pkgAcquire::Queue::QItem *Itm = 0;
227 if (URI.empty() == false)
228 Itm = OwnerQ->FindItem(URI,this);
229
230 // update used mirror
231 string UsedMirror = LookupTag(Message,"UsedMirror", "");
232 if (!UsedMirror.empty() &&
233 Itm &&
234 Itm->Description.find(" ") != string::npos)
235 {
236 Itm->Description.replace(0, Itm->Description.find(" "), UsedMirror);
237 // FIXME: will we need this as well?
238 //Itm->ShortDesc = UsedMirror;
239 }
240
241 // Determine the message number and dispatch
242 switch (Number)
243 {
244 // 100 Capabilities
245 case 100:
246 if (Capabilities(Message) == false)
247 return _error->Error("Unable to process Capabilities message from %s",Access.c_str());
248 break;
249
250 // 101 Log
251 case 101:
252 if (Debug == true)
253 clog << " <- (log) " << LookupTag(Message,"Message") << endl;
254 break;
255
256 // 102 Status
257 case 102:
258 Status = LookupTag(Message,"Message");
259 break;
260
261 // 103 Redirect
262 case 103:
263 {
264 if (Itm == 0)
265 {
266 _error->Error("Method gave invalid 103 Redirect message");
267 break;
268 }
269
270 string NewURI = LookupTag(Message,"New-URI",URI.c_str());
271 Itm->URI = NewURI;
272
273 ItemDone();
274
275 pkgAcquire::Item *Owner = Itm->Owner;
276 pkgAcquire::ItemDesc Desc = *Itm;
277
278 // Change the status so that it can be dequeued
279 Owner->Status = pkgAcquire::Item::StatIdle;
280 // Mark the item as done (taking care of all queues)
281 // and then put it in the main queue again
282 OwnerQ->ItemDone(Itm);
283 OwnerQ->Owner->Enqueue(Desc);
284
285 if (Log != 0)
286 Log->Done(Desc);
287 break;
288 }
289
290 // 200 URI Start
291 case 200:
292 {
293 if (Itm == 0)
294 {
295 _error->Error("Method gave invalid 200 URI Start message");
296 break;
297 }
298
299 CurrentItem = Itm;
300 CurrentSize = 0;
301 TotalSize = strtoull(LookupTag(Message,"Size","0").c_str(), NULL, 10);
302 ResumePoint = strtoull(LookupTag(Message,"Resume-Point","0").c_str(), NULL, 10);
303 Itm->Owner->Start(Message,strtoull(LookupTag(Message,"Size","0").c_str(), NULL, 10));
304
305 // Display update before completion
306 if (Log != 0 && Log->MorePulses == true)
307 Log->Pulse(Itm->Owner->GetOwner());
308
309 if (Log != 0)
310 Log->Fetch(*Itm);
311
312 break;
313 }
314
315 // 201 URI Done
316 case 201:
317 {
318 if (Itm == 0)
319 {
320 _error->Error("Method gave invalid 201 URI Done message");
321 break;
322 }
323
324 pkgAcquire::Item *Owner = Itm->Owner;
325 pkgAcquire::ItemDesc Desc = *Itm;
326
327 if (RealFileExists(Owner->DestFile))
328 ChangeOwnerAndPermissionOfFile("201::URIDone", Owner->DestFile.c_str(), "root", "root", 0644);
329
330 // Display update before completion
331 if (Log != 0 && Log->MorePulses == true)
332 Log->Pulse(Owner->GetOwner());
333
334 OwnerQ->ItemDone(Itm);
335 unsigned long long const ServerSize = strtoull(LookupTag(Message,"Size","0").c_str(), NULL, 10);
336 bool isHit = StringToBool(LookupTag(Message,"IMS-Hit"),false) ||
337 StringToBool(LookupTag(Message,"Alt-IMS-Hit"),false);
338 // Using the https method the server might return 200, but the
339 // If-Modified-Since condition is not satsified, libcurl will
340 // discard the download. In this case, however, TotalSize will be
341 // set to the actual size of the file, while ServerSize will be set
342 // to 0. Therefore, if the item is marked as a hit and the
343 // downloaded size (ServerSize) is 0, we ignore TotalSize.
344 if (TotalSize != 0 && (!isHit || ServerSize != 0) && ServerSize != TotalSize)
345 _error->Warning("Size of file %s is not what the server reported %s %llu",
346 Owner->DestFile.c_str(), LookupTag(Message,"Size","0").c_str(),TotalSize);
347
348 // see if there is a hash to verify
349 HashStringList ReceivedHashes;
350 HashStringList expectedHashes = Owner->HashSums();
351 for (HashStringList::const_iterator hs = expectedHashes.begin(); hs != expectedHashes.end(); ++hs)
352 {
353 std::string const tagname = hs->HashType() + "-Hash";
354 std::string const hashsum = LookupTag(Message, tagname.c_str());
355 if (hashsum.empty() == false)
356 ReceivedHashes.push_back(HashString(hs->HashType(), hashsum));
357 }
358
359 if(_config->FindB("Debug::pkgAcquire::Auth", false) == true)
360 {
361 std::clog << "201 URI Done: " << Owner->DescURI() << endl
362 << "ReceivedHash:" << endl;
363 for (HashStringList::const_iterator hs = ReceivedHashes.begin(); hs != ReceivedHashes.end(); ++hs)
364 std::clog << "\t- " << hs->toStr() << std::endl;
365 std::clog << "ExpectedHash:" << endl;
366 for (HashStringList::const_iterator hs = expectedHashes.begin(); hs != expectedHashes.end(); ++hs)
367 std::clog << "\t- " << hs->toStr() << std::endl;
368 std::clog << endl;
369 }
370 Owner->Done(Message, ServerSize, ReceivedHashes, Config);
371 ItemDone();
372
373 // Log that we are done
374 if (Log != 0)
375 {
376 if (isHit)
377 {
378 /* Hide 'hits' for local only sources - we also manage to
379 hide gets */
380 if (Config->LocalOnly == false)
381 Log->IMSHit(Desc);
382 }
383 else
384 Log->Done(Desc);
385 }
386 break;
387 }
388
389 // 400 URI Failure
390 case 400:
391 {
392 if (Itm == 0)
393 {
394 std::string const msg = LookupTag(Message,"Message");
395 _error->Error("Method gave invalid 400 URI Failure message: %s", msg.c_str());
396 break;
397 }
398
399 // Display update before completion
400 if (Log != 0 && Log->MorePulses == true)
401 Log->Pulse(Itm->Owner->GetOwner());
402
403 pkgAcquire::Item *Owner = Itm->Owner;
404 pkgAcquire::ItemDesc Desc = *Itm;
405
406 if (RealFileExists(Owner->DestFile))
407 ChangeOwnerAndPermissionOfFile("400::URIFailure", Owner->DestFile.c_str(), "root", "root", 0644);
408
409 OwnerQ->ItemDone(Itm);
410
411 // set some status
412 if(LookupTag(Message,"FailReason") == "Timeout" ||
413 LookupTag(Message,"FailReason") == "TmpResolveFailure" ||
414 LookupTag(Message,"FailReason") == "ResolveFailure" ||
415 LookupTag(Message,"FailReason") == "ConnectionRefused")
416 Owner->Status = pkgAcquire::Item::StatTransientNetworkError;
417
418 Owner->Failed(Message,Config);
419 ItemDone();
420
421 if (Log != 0)
422 Log->Fail(Desc);
423
424 break;
425 }
426
427 // 401 General Failure
428 case 401:
429 _error->Error("Method %s General failure: %s",Access.c_str(),LookupTag(Message,"Message").c_str());
430 break;
431
432 // 403 Media Change
433 case 403:
434 MediaChange(Message);
435 break;
436 }
437 }
438 return true;
439 }
440 /*}}}*/
441 // Worker::Capabilities - 100 Capabilities handler /*{{{*/
442 // ---------------------------------------------------------------------
443 /* This parses the capabilities message and dumps it into the configuration
444 structure. */
445 bool pkgAcquire::Worker::Capabilities(string Message)
446 {
447 if (Config == 0)
448 return true;
449
450 Config->Version = LookupTag(Message,"Version");
451 Config->SingleInstance = StringToBool(LookupTag(Message,"Single-Instance"),false);
452 Config->Pipeline = StringToBool(LookupTag(Message,"Pipeline"),false);
453 Config->SendConfig = StringToBool(LookupTag(Message,"Send-Config"),false);
454 Config->LocalOnly = StringToBool(LookupTag(Message,"Local-Only"),false);
455 Config->NeedsCleanup = StringToBool(LookupTag(Message,"Needs-Cleanup"),false);
456 Config->Removable = StringToBool(LookupTag(Message,"Removable"),false);
457
458 // Some debug text
459 if (Debug == true)
460 {
461 clog << "Configured access method " << Config->Access << endl;
462 clog << "Version:" << Config->Version <<
463 " SingleInstance:" << Config->SingleInstance <<
464 " Pipeline:" << Config->Pipeline <<
465 " SendConfig:" << Config->SendConfig <<
466 " LocalOnly: " << Config->LocalOnly <<
467 " NeedsCleanup: " << Config->NeedsCleanup <<
468 " Removable: " << Config->Removable << endl;
469 }
470
471 return true;
472 }
473 /*}}}*/
474 // Worker::MediaChange - Request a media change /*{{{*/
475 // ---------------------------------------------------------------------
476 /* */
477 bool pkgAcquire::Worker::MediaChange(string Message)
478 {
479 int status_fd = _config->FindI("APT::Status-Fd",-1);
480 if(status_fd > 0)
481 {
482 string Media = LookupTag(Message,"Media");
483 string Drive = LookupTag(Message,"Drive");
484 ostringstream msg,status;
485 ioprintf(msg,_("Please insert the disc labeled: "
486 "'%s' "
487 "in the drive '%s' and press enter."),
488 Media.c_str(),Drive.c_str());
489 status << "media-change: " // message
490 << Media << ":" // media
491 << Drive << ":" // drive
492 << msg.str() // l10n message
493 << endl;
494
495 std::string const dlstatus = status.str();
496 FileFd::Write(status_fd, dlstatus.c_str(), dlstatus.size());
497 }
498
499 if (Log == 0 || Log->MediaChange(LookupTag(Message,"Media"),
500 LookupTag(Message,"Drive")) == false)
501 {
502 char S[300];
503 snprintf(S,sizeof(S),"603 Media Changed\nFailed: true\n\n");
504 if (Debug == true)
505 clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl;
506 OutQueue += S;
507 OutReady = true;
508 return true;
509 }
510
511 char S[300];
512 snprintf(S,sizeof(S),"603 Media Changed\n\n");
513 if (Debug == true)
514 clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl;
515 OutQueue += S;
516 OutReady = true;
517 return true;
518 }
519 /*}}}*/
520 // Worker::SendConfiguration - Send the config to the method /*{{{*/
521 // ---------------------------------------------------------------------
522 /* */
523 bool pkgAcquire::Worker::SendConfiguration()
524 {
525 if (Config->SendConfig == false)
526 return true;
527
528 if (OutFd == -1)
529 return false;
530
531 /* Write out all of the configuration directives by walking the
532 configuration tree */
533 std::ostringstream Message;
534 Message << "601 Configuration\n";
535 _config->Dump(Message, NULL, "Config-Item: %F=%V\n", false);
536 Message << '\n';
537
538 if (Debug == true)
539 clog << " -> " << Access << ':' << QuoteString(Message.str(),"\n") << endl;
540 OutQueue += Message.str();
541 OutReady = true;
542
543 return true;
544 }
545 /*}}}*/
546 // Worker::QueueItem - Add an item to the outbound queue /*{{{*/
547 // ---------------------------------------------------------------------
548 /* Send a URI Acquire message to the method */
549 bool pkgAcquire::Worker::QueueItem(pkgAcquire::Queue::QItem *Item)
550 {
551 if (OutFd == -1)
552 return false;
553
554 string Message = "600 URI Acquire\n";
555 Message.reserve(300);
556 Message += "URI: " + Item->URI;
557 Message += "\nFilename: " + Item->Owner->DestFile;
558 HashStringList const hsl = Item->Owner->HashSums();
559 for (HashStringList::const_iterator hs = hsl.begin(); hs != hsl.end(); ++hs)
560 Message += "\nExpected-" + hs->HashType() + ": " + hs->HashValue();
561 if(Item->Owner->FileSize > 0)
562 {
563 string MaximumSize;
564 strprintf(MaximumSize, "%llu", Item->Owner->FileSize);
565 Message += "\nMaximum-Size: " + MaximumSize;
566 }
567 Message += Item->Owner->Custom600Headers();
568 Message += "\n\n";
569
570 if (RealFileExists(Item->Owner->DestFile))
571 {
572 std::string SandboxUser = _config->Find("APT::Sandbox::User");
573 ChangeOwnerAndPermissionOfFile("Item::QueueURI", Item->Owner->DestFile.c_str(),
574 SandboxUser.c_str(), "root", 0600);
575 }
576
577 if (Debug == true)
578 clog << " -> " << Access << ':' << QuoteString(Message,"\n") << endl;
579 OutQueue += Message;
580 OutReady = true;
581
582 return true;
583 }
584 /*}}}*/
585 // Worker::OutFdRead - Out bound FD is ready /*{{{*/
586 // ---------------------------------------------------------------------
587 /* */
588 bool pkgAcquire::Worker::OutFdReady()
589 {
590 int Res;
591 do
592 {
593 Res = write(OutFd,OutQueue.c_str(),OutQueue.length());
594 }
595 while (Res < 0 && errno == EINTR);
596
597 if (Res <= 0)
598 return MethodFailure();
599
600 OutQueue.erase(0,Res);
601 if (OutQueue.empty() == true)
602 OutReady = false;
603
604 return true;
605 }
606 /*}}}*/
607 // Worker::InFdRead - In bound FD is ready /*{{{*/
608 // ---------------------------------------------------------------------
609 /* */
610 bool pkgAcquire::Worker::InFdReady()
611 {
612 if (ReadMessages() == false)
613 return false;
614 RunMessages();
615 return true;
616 }
617 /*}}}*/
618 // Worker::MethodFailure - Called when the method fails /*{{{*/
619 // ---------------------------------------------------------------------
620 /* This is called when the method is believed to have failed, probably because
621 read returned -1. */
622 bool pkgAcquire::Worker::MethodFailure()
623 {
624 _error->Error("Method %s has died unexpectedly!",Access.c_str());
625
626 // do not reap the child here to show meaningfull error to the user
627 ExecWait(Process,Access.c_str(),false);
628 Process = -1;
629 close(InFd);
630 close(OutFd);
631 InFd = -1;
632 OutFd = -1;
633 OutReady = false;
634 InReady = false;
635 OutQueue = string();
636 MessageQueue.erase(MessageQueue.begin(),MessageQueue.end());
637
638 return false;
639 }
640 /*}}}*/
641 // Worker::Pulse - Called periodically /*{{{*/
642 // ---------------------------------------------------------------------
643 /* */
644 void pkgAcquire::Worker::Pulse()
645 {
646 if (CurrentItem == 0)
647 return;
648
649 struct stat Buf;
650 if (stat(CurrentItem->Owner->DestFile.c_str(),&Buf) != 0)
651 return;
652 CurrentSize = Buf.st_size;
653
654 // Hmm? Should not happen...
655 if (CurrentSize > TotalSize && TotalSize != 0)
656 TotalSize = CurrentSize;
657 }
658 /*}}}*/
659 // Worker::ItemDone - Called when the current item is finished /*{{{*/
660 // ---------------------------------------------------------------------
661 /* */
662 void pkgAcquire::Worker::ItemDone()
663 {
664 CurrentItem = 0;
665 CurrentSize = 0;
666 TotalSize = 0;
667 Status = string();
668 }
669 /*}}}*/