- apply Progeny's redir and authentication patch
[apt.git] / apt-pkg / acquire-worker.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description                                                          /*{{{*/
3 // $Id: acquire-worker.cc,v 1.1 2002/07/23 17:54:50 niemeyer 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 #ifdef __GNUG__
16 #pragma implementation "apt-pkg/acquire-worker.h"
17 #endif
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
25 #include <apti18n.h>
26
27 #include <iostream>
28 #include <fstream>
29     
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <signal.h>
34 #include <stdio.h>
35 #include <errno.h>
36                                                                         /*}}}*/
37
38 using namespace std;
39
40 // Worker::Worker - Constructor for Queue startup                       /*{{{*/
41 // ---------------------------------------------------------------------
42 /* */
43 pkgAcquire::Worker::Worker(Queue *Q,MethodConfig *Cnf,
44                            pkgAcquireStatus *Log) : Log(Log)
45 {
46    OwnerQ = Q;
47    Config = Cnf;
48    Access = Cnf->Access;
49    CurrentItem = 0;
50    TotalSize = 0;
51    CurrentSize = 0;
52    
53    Construct();   
54 }
55                                                                         /*}}}*/
56 // Worker::Worker - Constructor for method config startup               /*{{{*/
57 // ---------------------------------------------------------------------
58 /* */
59 pkgAcquire::Worker::Worker(MethodConfig *Cnf)
60 {
61    OwnerQ = 0;
62    Config = Cnf;
63    Access = Cnf->Access;
64    CurrentItem = 0;
65    TotalSize = 0;
66    CurrentSize = 0;
67    
68    Construct();   
69 }
70                                                                         /*}}}*/
71 // Worker::Construct - Constructor helper                               /*{{{*/
72 // ---------------------------------------------------------------------
73 /* */
74 void pkgAcquire::Worker::Construct()
75 {
76    NextQueue = 0;
77    NextAcquire = 0;
78    Process = -1;
79    InFd = -1;
80    OutFd = -1;
81    OutReady = false;
82    InReady = false;
83    Debug = _config->FindB("Debug::pkgAcquire::Worker",false);
84 }
85                                                                         /*}}}*/
86 // Worker::~Worker - Destructor                                         /*{{{*/
87 // ---------------------------------------------------------------------
88 /* */
89 pkgAcquire::Worker::~Worker()
90 {
91    close(InFd);
92    close(OutFd);
93    
94    if (Process > 0)
95    {
96       /* Closing of stdin is the signal to exit and die when the process
97          indicates it needs cleanup */
98       if (Config->NeedsCleanup == false)
99          kill(Process,SIGINT);
100       ExecWait(Process,Access.c_str(),true);
101    }   
102 }
103                                                                         /*}}}*/
104 // Worker::Start - Start the worker process                             /*{{{*/
105 // ---------------------------------------------------------------------
106 /* This forks the method and inits the communication channel */
107 bool pkgAcquire::Worker::Start()
108 {
109    // Get the method path
110    string Method = _config->FindDir("Dir::Bin::Methods") + Access;
111    if (FileExists(Method) == false)
112       return _error->Error(_("The method driver %s could not be found."),Method.c_str());
113
114    if (Debug == true)
115       clog << "Starting method '" << Method << '\'' << endl;
116
117    // Create the pipes
118    int Pipes[4] = {-1,-1,-1,-1};
119    if (pipe(Pipes) != 0 || pipe(Pipes+2) != 0)
120    {
121       _error->Errno("pipe","Failed to create IPC pipe to subprocess");
122       for (int I = 0; I != 4; I++)
123          close(Pipes[I]);
124       return false;
125    }
126    for (int I = 0; I != 4; I++)
127       SetCloseExec(Pipes[I],true);
128    
129    // Fork off the process
130    Process = ExecFork();
131    if (Process == 0)
132    {
133       // Setup the FDs
134       dup2(Pipes[1],STDOUT_FILENO);
135       dup2(Pipes[2],STDIN_FILENO);
136       SetCloseExec(STDOUT_FILENO,false);
137       SetCloseExec(STDIN_FILENO,false);      
138       SetCloseExec(STDERR_FILENO,false);
139       
140       const char *Args[2];
141       Args[0] = Method.c_str();
142       Args[1] = 0;
143       execv(Args[0],(char **)Args);
144       cerr << "Failed to exec method " << Args[0] << endl;
145       _exit(100);
146    }
147
148    // Fix up our FDs
149    InFd = Pipes[0];
150    OutFd = Pipes[3];
151    SetNonBlock(Pipes[0],true);
152    SetNonBlock(Pipes[3],true);
153    close(Pipes[1]);
154    close(Pipes[2]);
155    OutReady = false;
156    InReady = true;
157    
158    // Read the configuration data
159    if (WaitFd(InFd) == false ||
160        ReadMessages() == false)
161       return _error->Error(_("Method %s did not start correctly"),Method.c_str());
162
163    RunMessages();
164    if (OwnerQ != 0)
165       SendConfiguration();
166    
167    // CNC:2004-04-27
168    if (Config->HasPreferredURI == true &&
169        Config->DonePreferredURI == false &&
170        Config->PreferredURI.empty() == true) {
171       SetNonBlock(InFd,false);
172       SetNonBlock(OutFd,false);
173       OutQueue += "679 Preferred URI\n\n";
174       Config->PreferredURI = "<none>";
175       if (OutFdReady() == true)
176          while (InFdReady() == true && Config->PreferredURI == "<none>");
177       SetNonBlock(InFd,true);
178       SetNonBlock(OutFd,true);
179    }
180
181    return true;
182 }
183                                                                         /*}}}*/
184 // Worker::ReadMessages - Read all pending messages into the list       /*{{{*/
185 // ---------------------------------------------------------------------
186 /* */
187 bool pkgAcquire::Worker::ReadMessages()
188 {
189    if (::ReadMessages(InFd,MessageQueue) == false)
190       return MethodFailure();
191    return true;
192 }
193                                                                         /*}}}*/
194 // Worker::RunMessage - Empty the message queue                         /*{{{*/
195 // ---------------------------------------------------------------------
196 /* This takes the messages from the message queue and runs them through
197    the parsers in order. */
198 bool pkgAcquire::Worker::RunMessages()
199 {
200    while (MessageQueue.empty() == false)
201    {
202       string Message = MessageQueue.front();
203       MessageQueue.erase(MessageQueue.begin());
204
205       if (Debug == true)
206          clog << " <- " << Access << ':' << QuoteString(Message,"\n") << endl;
207       
208       // Fetch the message number
209       char *End;
210       int Number = strtol(Message.c_str(),&End,10);
211       if (End == Message.c_str())
212          return _error->Error("Invalid message from method %s: %s",Access.c_str(),Message.c_str());
213
214       string URI = LookupTag(Message,"URI");
215       pkgAcquire::Queue::QItem *Itm = 0;
216       if (URI.empty() == false)
217          Itm = OwnerQ->FindItem(URI,this);
218       
219       // Determine the message number and dispatch
220       switch (Number)
221       {
222          // 100 Capabilities
223          case 100:
224          if (Capabilities(Message) == false)
225             return _error->Error("Unable to process Capabilities message from %s",Access.c_str());
226          break;
227          
228          // 101 Log
229          case 101:
230          if (Debug == true)
231             clog << " <- (log) " << LookupTag(Message,"Message") << endl;
232          break;
233          
234          // 102 Status
235          case 102:
236          Status = LookupTag(Message,"Message");
237          break;
238
239          // CNC:2004-04-27
240          // 179 Preferred URI
241          case 179:
242          Config->PreferredURI = LookupTag(Message, "PreferredURI");
243          break;
244
245          // 103 Redirect
246          case 103:
247          {
248             if (Itm == 0)
249             {
250                _error->Error("Method gave invalid 103 Redirect message");
251                break;
252             }
253
254             string NewURI = LookupTag(Message,"New-URI",URI.c_str());
255             Itm->URI = NewURI;
256             break;
257          }
258             
259          // 200 URI Start
260          case 200:
261          {
262             if (Itm == 0)
263             {
264                _error->Error("Method gave invalid 200 URI Start message");
265                break;
266             }
267             
268             CurrentItem = Itm;
269             CurrentSize = 0;
270             TotalSize = atoi(LookupTag(Message,"Size","0").c_str());
271             ResumePoint = atoi(LookupTag(Message,"Resume-Point","0").c_str());
272             Itm->Owner->Start(Message,atoi(LookupTag(Message,"Size","0").c_str()));
273
274             // Display update before completion
275             if (Log != 0 && Log->MorePulses == true)
276                Log->Pulse(Itm->Owner->GetOwner());
277             
278             if (Log != 0)
279                Log->Fetch(*Itm);
280
281             break;
282          }
283          
284          // 201 URI Done
285          case 201:
286          {
287             if (Itm == 0)
288             {
289                _error->Error("Method gave invalid 201 URI Done message");
290                break;
291             }
292             
293             pkgAcquire::Item *Owner = Itm->Owner;
294             pkgAcquire::ItemDesc Desc = *Itm;
295             
296             // Display update before completion
297             if (Log != 0 && Log->MorePulses == true)
298                Log->Pulse(Owner->GetOwner());
299             
300             OwnerQ->ItemDone(Itm);
301             if (TotalSize != 0 &&
302                 (unsigned)atoi(LookupTag(Message,"Size","0").c_str()) != TotalSize)
303                _error->Warning("Bizarre Error - File size is not what the server reported %s %lu",
304                                LookupTag(Message,"Size","0").c_str(),TotalSize);
305
306             Owner->Done(Message,atoi(LookupTag(Message,"Size","0").c_str()),
307                         LookupTag(Message,"MD5-Hash"),Config);
308             ItemDone();
309             
310             // Log that we are done
311             if (Log != 0)
312             {
313                if (StringToBool(LookupTag(Message,"IMS-Hit"),false) == true ||
314                    StringToBool(LookupTag(Message,"Alt-IMS-Hit"),false) == true)
315                {
316                   /* Hide 'hits' for local only sources - we also manage to
317                      hide gets */
318                   if (Config->LocalOnly == false)
319                      Log->IMSHit(Desc);
320                }               
321                else
322                   Log->Done(Desc);
323             }
324             break;
325          }       
326          
327          // 400 URI Failure
328          case 400:
329          {
330             if (Itm == 0)
331             {
332                _error->Error("Method gave invalid 400 URI Failure message");
333                break;
334             }
335
336             // Display update before completion
337             if (Log != 0 && Log->MorePulses == true)
338                Log->Pulse(Itm->Owner->GetOwner());
339             
340             pkgAcquire::Item *Owner = Itm->Owner;
341             pkgAcquire::ItemDesc Desc = *Itm;
342             OwnerQ->ItemDone(Itm);
343             Owner->Failed(Message,Config);
344             ItemDone();
345
346             if (Log != 0)
347                Log->Fail(Desc);
348
349             break;
350          }       
351          
352          // 401 General Failure
353          case 401:
354          _error->Error("Method %s General failure: %s",Access.c_str(),LookupTag(Message,"Message").c_str());
355          break;
356          
357          // 403 Media Change
358          case 403:
359          MediaChange(Message); 
360          break;
361
362          // 404 Authenticate
363          case 404:
364          Authenticate(Message);
365          break;
366       }      
367    }
368    return true;
369 }
370                                                                         /*}}}*/
371 // Worker::Capabilities - 100 Capabilities handler                      /*{{{*/
372 // ---------------------------------------------------------------------
373 /* This parses the capabilities message and dumps it into the configuration
374    structure. */
375 bool pkgAcquire::Worker::Capabilities(string Message)
376 {
377    if (Config == 0)
378       return true;
379    
380    Config->Version = LookupTag(Message,"Version");
381    Config->SingleInstance = StringToBool(LookupTag(Message,"Single-Instance"),false);
382    Config->Pipeline = StringToBool(LookupTag(Message,"Pipeline"),false);
383    Config->SendConfig = StringToBool(LookupTag(Message,"Send-Config"),false);
384    Config->LocalOnly = StringToBool(LookupTag(Message,"Local-Only"),false);
385    Config->NeedsCleanup = StringToBool(LookupTag(Message,"Needs-Cleanup"),false);
386    Config->Removable = StringToBool(LookupTag(Message,"Removable"),false);
387    // CNC:2004-04-27
388    Config->HasPreferredURI = StringToBool(LookupTag(Message,"Has-Preferred-URI"),false);
389
390    // Some debug text
391    if (Debug == true)
392    {
393       clog << "Configured access method " << Config->Access << endl;
394       clog << "Version:" << Config->Version <<
395               " SingleInstance:" << Config->SingleInstance <<
396               " Pipeline:" << Config->Pipeline << 
397               " SendConfig:" << Config->SendConfig << 
398               " LocalOnly: " << Config->LocalOnly << 
399               " NeedsCleanup: " << Config->NeedsCleanup << 
400               // CNC:2004-04-27
401               " Removable: " << Config->Removable <<
402               " HasPreferredURI: " << Config->HasPreferredURI << endl;
403    }
404    
405    return true;
406 }
407                                                                         /*}}}*/
408 // Worker::MediaChange - Request a media change                         /*{{{*/
409 // ---------------------------------------------------------------------
410 /* */
411 bool pkgAcquire::Worker::MediaChange(string Message)
412 {
413    if (Log == 0 || Log->MediaChange(LookupTag(Message,"Media"),
414                                     LookupTag(Message,"Drive")) == false)
415    {
416       char S[300];
417       snprintf(S,sizeof(S),"603 Media Changed\nFailed: true\n\n");
418       if (Debug == true)
419          clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl;
420       OutQueue += S;
421       OutReady = true;
422       return true;
423    }
424
425    char S[300];
426    snprintf(S,sizeof(S),"603 Media Changed\n\n");
427    if (Debug == true)
428       clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl;
429    OutQueue += S;
430    OutReady = true;
431    return true;
432 }
433                                                                         /*}}}*/
434 // Worker::Authenticate - Request authentication                        /*{{{*/
435 // ---------------------------------------------------------------------
436 /* */
437 bool pkgAcquire::Worker::Authenticate(string Message)
438 {
439    string User, Pass;
440    if (Log == 0 || Log->Authenticate(LookupTag(Message,"Description"),
441                                      User,Pass) == false)
442    {
443       char S[300];
444       snprintf(S,sizeof(S),"604 Authenticated\nFailed: true\n\n");
445       if (Debug == true)
446          clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl;
447       OutQueue += S;
448       OutReady = true;
449       return true;
450    }
451
452    char S[300];
453    snprintf(S,sizeof(S),"604 Authenticated\nUser: %s\nPassword: %s\n\n",
454             User.c_str(), Pass.c_str());
455    if (Debug == true)
456       clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl;
457    OutQueue += S;
458    OutReady = true;
459    return true;
460 }
461                                                                         /*}}}*/
462 // Worker::SendConfiguration - Send the config to the method            /*{{{*/
463 // ---------------------------------------------------------------------
464 /* */
465 bool pkgAcquire::Worker::SendConfiguration()
466 {
467    if (Config->SendConfig == false)
468       return true;
469
470    if (OutFd == -1)
471       return false;
472    
473    string Message = "601 Configuration\n";
474    Message.reserve(2000);
475
476    /* Write out all of the configuration directives by walking the 
477       configuration tree */
478    const Configuration::Item *Top = _config->Tree(0);
479    for (; Top != 0;)
480    {
481       if (Top->Value.empty() == false)
482       {
483          string Line = "Config-Item: " + QuoteString(Top->FullTag(),"=\"\n") + "=";
484          Line += QuoteString(Top->Value,"\n") + '\n';
485          Message += Line;
486       }
487       
488       if (Top->Child != 0)
489       {
490          Top = Top->Child;
491          continue;
492       }
493       
494       while (Top != 0 && Top->Next == 0)
495          Top = Top->Parent;
496       if (Top != 0)
497          Top = Top->Next;
498    }   
499    Message += '\n';
500
501    if (Debug == true)
502       clog << " -> " << Access << ':' << QuoteString(Message,"\n") << endl;
503    OutQueue += Message;
504    OutReady = true; 
505    
506    return true;
507 }
508                                                                         /*}}}*/
509 // Worker::QueueItem - Add an item to the outbound queue                /*{{{*/
510 // ---------------------------------------------------------------------
511 /* Send a URI Acquire message to the method */
512 bool pkgAcquire::Worker::QueueItem(pkgAcquire::Queue::QItem *Item)
513 {
514    if (OutFd == -1)
515       return false;
516    
517    string Message = "600 URI Acquire\n";
518    Message.reserve(300);
519    Message += "URI: " + Item->URI;
520    Message += "\nFilename: " + Item->Owner->DestFile;
521    Message += Item->Owner->Custom600Headers();
522    Message += "\n\n";
523    
524    if (Debug == true)
525       clog << " -> " << Access << ':' << QuoteString(Message,"\n") << endl;
526    OutQueue += Message;
527    OutReady = true;
528    
529    return true;
530 }
531                                                                         /*}}}*/
532 // Worker::OutFdRead - Out bound FD is ready                            /*{{{*/
533 // ---------------------------------------------------------------------
534 /* */
535 bool pkgAcquire::Worker::OutFdReady()
536 {
537    int Res;
538    do
539    {
540       Res = write(OutFd,OutQueue.c_str(),OutQueue.length());
541    }
542    while (Res < 0 && errno == EINTR);
543    
544    if (Res <= 0)
545       return MethodFailure();
546
547    // Hmm.. this should never happen.
548    if (Res < 0)
549       return true;
550    
551    OutQueue.erase(0,Res);
552    if (OutQueue.empty() == true)
553       OutReady = false;
554    
555    return true;
556 }
557                                                                         /*}}}*/
558 // Worker::InFdRead - In bound FD is ready                              /*{{{*/
559 // ---------------------------------------------------------------------
560 /* */
561 bool pkgAcquire::Worker::InFdReady()
562 {
563    if (ReadMessages() == false)
564       return false;
565    RunMessages();
566    return true;
567 }
568                                                                         /*}}}*/
569 // Worker::MethodFailure - Called when the method fails                 /*{{{*/
570 // ---------------------------------------------------------------------
571 /* This is called when the method is belived to have failed, probably because
572    read returned -1. */
573 bool pkgAcquire::Worker::MethodFailure()
574 {
575    _error->Error("Method %s has died unexpectedly!",Access.c_str());
576    
577    ExecWait(Process,Access.c_str(),true);
578    Process = -1;
579    close(InFd);
580    close(OutFd);
581    InFd = -1;
582    OutFd = -1;
583    OutReady = false;
584    InReady = false;
585    OutQueue = string();
586    MessageQueue.erase(MessageQueue.begin(),MessageQueue.end());
587    
588    return false;
589 }
590                                                                         /*}}}*/
591 // Worker::Pulse - Called periodically                                  /*{{{*/
592 // ---------------------------------------------------------------------
593 /* */
594 void pkgAcquire::Worker::Pulse()
595 {
596    if (CurrentItem == 0)
597       return;
598  
599    struct stat Buf;
600    if (stat(CurrentItem->Owner->DestFile.c_str(),&Buf) != 0)
601       return;
602    CurrentSize = Buf.st_size;
603    
604    // Hmm? Should not happen...
605    if (CurrentSize > TotalSize && TotalSize != 0)
606       TotalSize = CurrentSize;
607 }
608                                                                         /*}}}*/
609 // Worker::ItemDone - Called when the current item is finished          /*{{{*/
610 // ---------------------------------------------------------------------
611 /* */
612 void pkgAcquire::Worker::ItemDone()
613 {
614    CurrentItem = 0;
615    CurrentSize = 0;
616    TotalSize = 0;
617    Status = string();
618 }
619                                                                         /*}}}*/
620
621 // vim:sts=3:sw=3