]> git.saurik.com Git - apt.git/blob - apt-pkg/acquire-worker.cc
rework hashsum verification in the acquire system
[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 // Worker::Worker - Constructor for Queue startup /*{{{*/
47 // ---------------------------------------------------------------------
48 /* */
49 pkgAcquire::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 /* */
65 pkgAcquire::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 /* */
80 void 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 /* */
95 pkgAcquire::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 */
113 bool 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 /* */
184 bool 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. */
195 bool 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 pkgAcquire::Item *Owner = Itm->Owner;
262 pkgAcquire::ItemDesc Desc = *Itm;
263
264 // Change the status so that it can be dequeued
265 Owner->Status = pkgAcquire::Item::StatIdle;
266 // Mark the item as done (taking care of all queues)
267 // and then put it in the main queue again
268 OwnerQ->ItemDone(Itm);
269 OwnerQ->Owner->Enqueue(Desc);
270
271 if (Log != 0)
272 Log->Done(Desc);
273 break;
274 }
275
276 // 200 URI Start
277 case 200:
278 {
279 if (Itm == 0)
280 {
281 _error->Error("Method gave invalid 200 URI Start message");
282 break;
283 }
284
285 CurrentItem = Itm;
286 CurrentSize = 0;
287 TotalSize = strtoull(LookupTag(Message,"Size","0").c_str(), NULL, 10);
288 ResumePoint = strtoull(LookupTag(Message,"Resume-Point","0").c_str(), NULL, 10);
289 Itm->Owner->Start(Message, TotalSize);
290
291 // Display update before completion
292 if (Log != 0 && Log->MorePulses == true)
293 Log->Pulse(Itm->Owner->GetOwner());
294
295 if (Log != 0)
296 Log->Fetch(*Itm);
297
298 break;
299 }
300
301 // 201 URI Done
302 case 201:
303 {
304 if (Itm == 0)
305 {
306 _error->Error("Method gave invalid 201 URI Done message");
307 break;
308 }
309
310 pkgAcquire::Item *Owner = Itm->Owner;
311 pkgAcquire::ItemDesc Desc = *Itm;
312
313 if (RealFileExists(Owner->DestFile))
314 ChangeOwnerAndPermissionOfFile("201::URIDone", Owner->DestFile.c_str(), "root", "root", 0644);
315
316 // Display update before completion
317 if (Log != 0 && Log->MorePulses == true)
318 Log->Pulse(Owner->GetOwner());
319
320 OwnerQ->ItemDone(Itm);
321
322 HashStringList const ExpectedHashes = Owner->GetExpectedHashes();
323 // see if we got hashes to verify
324 HashStringList ReceivedHashes;
325 for (char const * const * type = HashString::SupportedHashes(); *type != NULL; ++type)
326 {
327 std::string const tagname = std::string(*type) + "-Hash";
328 std::string const hashsum = LookupTag(Message, tagname.c_str());
329 if (hashsum.empty() == false)
330 ReceivedHashes.push_back(HashString(*type, hashsum));
331 }
332 // not all methods always sent Hashes our way
333 if (ExpectedHashes.usable() == true && ReceivedHashes.usable() == false)
334 {
335 std::string const filename = LookupTag(Message, "Filename", Owner->DestFile.c_str());
336 if (filename.empty() == false && RealFileExists(filename))
337 {
338 Hashes calc(ExpectedHashes);
339 FileFd file(filename, FileFd::ReadOnly, FileFd::None);
340 calc.AddFD(file);
341 ReceivedHashes = calc.GetHashStringList();
342 }
343 }
344
345 if(_config->FindB("Debug::pkgAcquire::Auth", false) == true)
346 {
347 std::clog << "201 URI Done: " << Owner->DescURI() << endl
348 << "ReceivedHash:" << endl;
349 for (HashStringList::const_iterator hs = ReceivedHashes.begin(); hs != ReceivedHashes.end(); ++hs)
350 std::clog << "\t- " << hs->toStr() << std::endl;
351 std::clog << "ExpectedHash:" << endl;
352 for (HashStringList::const_iterator hs = ExpectedHashes.begin(); hs != ExpectedHashes.end(); ++hs)
353 std::clog << "\t- " << hs->toStr() << std::endl;
354 std::clog << endl;
355 }
356
357 // decide if what we got is what we expected
358 bool consideredOkay = false;
359 bool const isIMSHit = StringToBool(LookupTag(Message,"IMS-Hit"),false) ||
360 StringToBool(LookupTag(Message,"Alt-IMS-Hit"),false);
361 if (ExpectedHashes.usable())
362 {
363 if (ReceivedHashes.usable() == false)
364 {
365 /* IMS-Hits can't be checked here as we will have uncompressed file,
366 but the hashes for the compressed file. What we have was good through
367 so all we have to ensure later is that we are not stalled. */
368 consideredOkay = isIMSHit;
369 }
370 else if (ReceivedHashes == ExpectedHashes)
371 consideredOkay = true;
372 else
373 consideredOkay = false;
374
375 }
376 else if (Owner->HashesRequired() == true)
377 consideredOkay = false;
378 else
379 consideredOkay = true;
380
381 if (consideredOkay == true)
382 {
383 Owner->Done(Message, ReceivedHashes, Config);
384 ItemDone();
385
386 // Log that we are done
387 if (Log != 0)
388 {
389 if (isIMSHit)
390 {
391 /* Hide 'hits' for local only sources - we also manage to
392 hide gets */
393 if (Config->LocalOnly == false)
394 Log->IMSHit(Desc);
395 }
396 else
397 Log->Done(Desc);
398 }
399 }
400 else
401 {
402 Owner->Status = pkgAcquire::Item::StatAuthError;
403 Owner->Failed(Message,Config);
404 ItemDone();
405
406 if (Log != 0)
407 Log->Fail(Desc);
408 }
409 break;
410 }
411
412 // 400 URI Failure
413 case 400:
414 {
415 if (Itm == 0)
416 {
417 std::string const msg = LookupTag(Message,"Message");
418 _error->Error("Method gave invalid 400 URI Failure message: %s", msg.c_str());
419 break;
420 }
421
422 // Display update before completion
423 if (Log != 0 && Log->MorePulses == true)
424 Log->Pulse(Itm->Owner->GetOwner());
425
426 pkgAcquire::Item *Owner = Itm->Owner;
427 pkgAcquire::ItemDesc Desc = *Itm;
428
429 if (RealFileExists(Owner->DestFile))
430 ChangeOwnerAndPermissionOfFile("400::URIFailure", Owner->DestFile.c_str(), "root", "root", 0644);
431
432 OwnerQ->ItemDone(Itm);
433
434 // set some status
435 if(LookupTag(Message,"FailReason") == "Timeout" ||
436 LookupTag(Message,"FailReason") == "TmpResolveFailure" ||
437 LookupTag(Message,"FailReason") == "ResolveFailure" ||
438 LookupTag(Message,"FailReason") == "ConnectionRefused")
439 Owner->Status = pkgAcquire::Item::StatTransientNetworkError;
440
441 Owner->Failed(Message,Config);
442 ItemDone();
443
444 if (Log != 0)
445 Log->Fail(Desc);
446
447 break;
448 }
449
450 // 401 General Failure
451 case 401:
452 _error->Error("Method %s General failure: %s",Access.c_str(),LookupTag(Message,"Message").c_str());
453 break;
454
455 // 403 Media Change
456 case 403:
457 MediaChange(Message);
458 break;
459 }
460 }
461 return true;
462 }
463 /*}}}*/
464 // Worker::Capabilities - 100 Capabilities handler /*{{{*/
465 // ---------------------------------------------------------------------
466 /* This parses the capabilities message and dumps it into the configuration
467 structure. */
468 bool pkgAcquire::Worker::Capabilities(string Message)
469 {
470 if (Config == 0)
471 return true;
472
473 Config->Version = LookupTag(Message,"Version");
474 Config->SingleInstance = StringToBool(LookupTag(Message,"Single-Instance"),false);
475 Config->Pipeline = StringToBool(LookupTag(Message,"Pipeline"),false);
476 Config->SendConfig = StringToBool(LookupTag(Message,"Send-Config"),false);
477 Config->LocalOnly = StringToBool(LookupTag(Message,"Local-Only"),false);
478 Config->NeedsCleanup = StringToBool(LookupTag(Message,"Needs-Cleanup"),false);
479 Config->Removable = StringToBool(LookupTag(Message,"Removable"),false);
480
481 // Some debug text
482 if (Debug == true)
483 {
484 clog << "Configured access method " << Config->Access << endl;
485 clog << "Version:" << Config->Version <<
486 " SingleInstance:" << Config->SingleInstance <<
487 " Pipeline:" << Config->Pipeline <<
488 " SendConfig:" << Config->SendConfig <<
489 " LocalOnly: " << Config->LocalOnly <<
490 " NeedsCleanup: " << Config->NeedsCleanup <<
491 " Removable: " << Config->Removable << endl;
492 }
493
494 return true;
495 }
496 /*}}}*/
497 // Worker::MediaChange - Request a media change /*{{{*/
498 // ---------------------------------------------------------------------
499 /* */
500 bool pkgAcquire::Worker::MediaChange(string Message)
501 {
502 int status_fd = _config->FindI("APT::Status-Fd",-1);
503 if(status_fd > 0)
504 {
505 string Media = LookupTag(Message,"Media");
506 string Drive = LookupTag(Message,"Drive");
507 ostringstream msg,status;
508 ioprintf(msg,_("Please insert the disc labeled: "
509 "'%s' "
510 "in the drive '%s' and press enter."),
511 Media.c_str(),Drive.c_str());
512 status << "media-change: " // message
513 << Media << ":" // media
514 << Drive << ":" // drive
515 << msg.str() // l10n message
516 << endl;
517
518 std::string const dlstatus = status.str();
519 FileFd::Write(status_fd, dlstatus.c_str(), dlstatus.size());
520 }
521
522 if (Log == 0 || Log->MediaChange(LookupTag(Message,"Media"),
523 LookupTag(Message,"Drive")) == false)
524 {
525 char S[300];
526 snprintf(S,sizeof(S),"603 Media Changed\nFailed: true\n\n");
527 if (Debug == true)
528 clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl;
529 OutQueue += S;
530 OutReady = true;
531 return true;
532 }
533
534 char S[300];
535 snprintf(S,sizeof(S),"603 Media Changed\n\n");
536 if (Debug == true)
537 clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl;
538 OutQueue += S;
539 OutReady = true;
540 return true;
541 }
542 /*}}}*/
543 // Worker::SendConfiguration - Send the config to the method /*{{{*/
544 // ---------------------------------------------------------------------
545 /* */
546 bool pkgAcquire::Worker::SendConfiguration()
547 {
548 if (Config->SendConfig == false)
549 return true;
550
551 if (OutFd == -1)
552 return false;
553
554 /* Write out all of the configuration directives by walking the
555 configuration tree */
556 std::ostringstream Message;
557 Message << "601 Configuration\n";
558 _config->Dump(Message, NULL, "Config-Item: %F=%V\n", false);
559 Message << '\n';
560
561 if (Debug == true)
562 clog << " -> " << Access << ':' << QuoteString(Message.str(),"\n") << endl;
563 OutQueue += Message.str();
564 OutReady = true;
565
566 return true;
567 }
568 /*}}}*/
569 // Worker::QueueItem - Add an item to the outbound queue /*{{{*/
570 // ---------------------------------------------------------------------
571 /* Send a URI Acquire message to the method */
572 bool pkgAcquire::Worker::QueueItem(pkgAcquire::Queue::QItem *Item)
573 {
574 if (OutFd == -1)
575 return false;
576
577 string Message = "600 URI Acquire\n";
578 Message.reserve(300);
579 Message += "URI: " + Item->URI;
580 Message += "\nFilename: " + Item->Owner->DestFile;
581 HashStringList const hsl = Item->Owner->GetExpectedHashes();
582 for (HashStringList::const_iterator hs = hsl.begin(); hs != hsl.end(); ++hs)
583 Message += "\nExpected-" + hs->HashType() + ": " + hs->HashValue();
584 if(Item->Owner->FileSize > 0)
585 {
586 string MaximumSize;
587 strprintf(MaximumSize, "%llu", Item->Owner->FileSize);
588 Message += "\nMaximum-Size: " + MaximumSize;
589 }
590 Message += Item->Owner->Custom600Headers();
591 Message += "\n\n";
592
593 if (RealFileExists(Item->Owner->DestFile))
594 {
595 std::string SandboxUser = _config->Find("APT::Sandbox::User");
596 ChangeOwnerAndPermissionOfFile("Item::QueueURI", Item->Owner->DestFile.c_str(),
597 SandboxUser.c_str(), "root", 0600);
598 }
599
600 if (Debug == true)
601 clog << " -> " << Access << ':' << QuoteString(Message,"\n") << endl;
602 OutQueue += Message;
603 OutReady = true;
604
605 return true;
606 }
607 /*}}}*/
608 // Worker::OutFdRead - Out bound FD is ready /*{{{*/
609 // ---------------------------------------------------------------------
610 /* */
611 bool pkgAcquire::Worker::OutFdReady()
612 {
613 int Res;
614 do
615 {
616 Res = write(OutFd,OutQueue.c_str(),OutQueue.length());
617 }
618 while (Res < 0 && errno == EINTR);
619
620 if (Res <= 0)
621 return MethodFailure();
622
623 OutQueue.erase(0,Res);
624 if (OutQueue.empty() == true)
625 OutReady = false;
626
627 return true;
628 }
629 /*}}}*/
630 // Worker::InFdRead - In bound FD is ready /*{{{*/
631 // ---------------------------------------------------------------------
632 /* */
633 bool pkgAcquire::Worker::InFdReady()
634 {
635 if (ReadMessages() == false)
636 return false;
637 RunMessages();
638 return true;
639 }
640 /*}}}*/
641 // Worker::MethodFailure - Called when the method fails /*{{{*/
642 // ---------------------------------------------------------------------
643 /* This is called when the method is believed to have failed, probably because
644 read returned -1. */
645 bool pkgAcquire::Worker::MethodFailure()
646 {
647 _error->Error("Method %s has died unexpectedly!",Access.c_str());
648
649 // do not reap the child here to show meaningfull error to the user
650 ExecWait(Process,Access.c_str(),false);
651 Process = -1;
652 close(InFd);
653 close(OutFd);
654 InFd = -1;
655 OutFd = -1;
656 OutReady = false;
657 InReady = false;
658 OutQueue = string();
659 MessageQueue.erase(MessageQueue.begin(),MessageQueue.end());
660
661 return false;
662 }
663 /*}}}*/
664 // Worker::Pulse - Called periodically /*{{{*/
665 // ---------------------------------------------------------------------
666 /* */
667 void pkgAcquire::Worker::Pulse()
668 {
669 if (CurrentItem == 0)
670 return;
671
672 struct stat Buf;
673 if (stat(CurrentItem->Owner->DestFile.c_str(),&Buf) != 0)
674 return;
675 CurrentSize = Buf.st_size;
676 }
677 /*}}}*/
678 // Worker::ItemDone - Called when the current item is finished /*{{{*/
679 // ---------------------------------------------------------------------
680 /* */
681 void pkgAcquire::Worker::ItemDone()
682 {
683 CurrentItem = 0;
684 CurrentSize = 0;
685 TotalSize = 0;
686 Status = string();
687 }
688 /*}}}*/