- initial import of revision 374 from cnc
[apt.git] / apt-pkg / deb / dpkgpm.cc
1 // Description                                                          /*{{{*/
2 // $Id: dpkgpm.cc,v 1.27 2003/07/26 00:25:44 mdz Exp $
3 /* ######################################################################
4
5    DPKG Package Manager - Provide an interface to dpkg
6    
7    ##################################################################### */
8                                                                         /*}}}*/
9 // Includes                                                             /*{{{*/
10 #ifdef __GNUG__
11 #pragma implementation "apt-pkg/dpkgpm.h"
12 #endif
13 #include <apt-pkg/dpkgpm.h>
14 #include <apt-pkg/error.h>
15 #include <apt-pkg/configuration.h>
16 #include <apt-pkg/depcache.h>
17 #include <apt-pkg/strutl.h>
18
19 #include <unistd.h>
20 #include <stdlib.h>
21 #include <fcntl.h>
22 #include <sys/types.h>
23 #include <sys/wait.h>
24 #include <signal.h>
25 #include <errno.h>
26 #include <stdio.h>
27 #include <iostream>
28                                                                         /*}}}*/
29
30 using namespace std;
31
32 // DPkgPM::pkgDPkgPM - Constructor                                      /*{{{*/
33 // ---------------------------------------------------------------------
34 /* */
35 pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) : pkgPackageManager(Cache)
36 {
37 }
38                                                                         /*}}}*/
39 // DPkgPM::pkgDPkgPM - Destructor                                       /*{{{*/
40 // ---------------------------------------------------------------------
41 /* */
42 pkgDPkgPM::~pkgDPkgPM()
43 {
44 }
45                                                                         /*}}}*/
46 // DPkgPM::Install - Install a package                                  /*{{{*/
47 // ---------------------------------------------------------------------
48 /* Add an install operation to the sequence list */
49 bool pkgDPkgPM::Install(PkgIterator Pkg,string File)
50 {
51    if (File.empty() == true || Pkg.end() == true)
52       return _error->Error("Internal Error, No file name for %s",Pkg.Name());
53
54    List.push_back(Item(Item::Install,Pkg,File));
55    return true;
56 }
57                                                                         /*}}}*/
58 // DPkgPM::Configure - Configure a package                              /*{{{*/
59 // ---------------------------------------------------------------------
60 /* Add a configure operation to the sequence list */
61 bool pkgDPkgPM::Configure(PkgIterator Pkg)
62 {
63    if (Pkg.end() == true)
64       return false;
65    
66    List.push_back(Item(Item::Configure,Pkg));
67    return true;
68 }
69                                                                         /*}}}*/
70 // DPkgPM::Remove - Remove a package                                    /*{{{*/
71 // ---------------------------------------------------------------------
72 /* Add a remove operation to the sequence list */
73 bool pkgDPkgPM::Remove(PkgIterator Pkg,bool Purge)
74 {
75    if (Pkg.end() == true)
76       return false;
77    
78    if (Purge == true)
79       List.push_back(Item(Item::Purge,Pkg));
80    else
81       List.push_back(Item(Item::Remove,Pkg));
82    return true;
83 }
84                                                                         /*}}}*/
85 // DPkgPM::RunScripts - Run a set of scripts                            /*{{{*/
86 // ---------------------------------------------------------------------
87 /* This looks for a list of script sto run from the configuration file,
88    each one is run with system from a forked child. */
89 bool pkgDPkgPM::RunScripts(const char *Cnf)
90 {
91    Configuration::Item const *Opts = _config->Tree(Cnf);
92    if (Opts == 0 || Opts->Child == 0)
93       return true;
94    Opts = Opts->Child;
95
96    // Fork for running the system calls
97    pid_t Child = ExecFork();
98    
99    // This is the child
100    if (Child == 0)
101    {
102       if (chdir("/tmp/") != 0)
103          _exit(100);
104          
105       unsigned int Count = 1;
106       for (; Opts != 0; Opts = Opts->Next, Count++)
107       {
108          if (Opts->Value.empty() == true)
109             continue;
110          
111          if (system(Opts->Value.c_str()) != 0)
112             _exit(100+Count);
113       }
114       _exit(0);
115    }      
116
117    // Wait for the child
118    int Status = 0;
119    while (waitpid(Child,&Status,0) != Child)
120    {
121       if (errno == EINTR)
122          continue;
123       return _error->Errno("waitpid","Couldn't wait for subprocess");
124    }
125
126    // Restore sig int/quit
127    signal(SIGQUIT,SIG_DFL);
128    signal(SIGINT,SIG_DFL);   
129
130    // Check for an error code.
131    if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
132    {
133       unsigned int Count = WEXITSTATUS(Status);
134       if (Count > 100)
135       {
136          Count -= 100;
137          for (; Opts != 0 && Count != 1; Opts = Opts->Next, Count--);
138          _error->Error("Problem executing scripts %s '%s'",Cnf,Opts->Value.c_str());
139       }
140       
141       return _error->Error("Sub-process returned an error code");
142    }
143    
144    return true;
145 }
146                                                                         /*}}}*/
147 // DPkgPM::SendV2Pkgs - Send version 2 package info                     /*{{{*/
148 // ---------------------------------------------------------------------
149 /* This is part of the helper script communication interface, it sends
150    very complete information down to the other end of the pipe.*/
151 bool pkgDPkgPM::SendV2Pkgs(FILE *F)
152 {
153    fprintf(F,"VERSION 2\n");
154    
155    /* Write out all of the configuration directives by walking the 
156       configuration tree */
157    const Configuration::Item *Top = _config->Tree(0);
158    for (; Top != 0;)
159    {
160       if (Top->Value.empty() == false)
161       {
162          fprintf(F,"%s=%s\n",
163                  QuoteString(Top->FullTag(),"=\"\n").c_str(),
164                  QuoteString(Top->Value,"\n").c_str());
165       }
166
167       if (Top->Child != 0)
168       {
169          Top = Top->Child;
170          continue;
171       }
172       
173       while (Top != 0 && Top->Next == 0)
174          Top = Top->Parent;
175       if (Top != 0)
176          Top = Top->Next;
177    }   
178    fprintf(F,"\n");
179  
180    // Write out the package actions in order.
181    for (vector<Item>::iterator I = List.begin(); I != List.end(); I++)
182    {
183       pkgDepCache::StateCache &S = Cache[I->Pkg];
184       
185       fprintf(F,"%s ",I->Pkg.Name());
186       // Current version
187       if (I->Pkg->CurrentVer == 0)
188          fprintf(F,"- ");
189       else
190          fprintf(F,"%s ",I->Pkg.CurrentVer().VerStr());
191       
192       // Show the compare operator
193       // Target version
194       if (S.InstallVer != 0)
195       {
196          int Comp = 2;
197          if (I->Pkg->CurrentVer != 0)
198             Comp = S.InstVerIter(Cache).CompareVer(I->Pkg.CurrentVer());
199          if (Comp < 0)
200             fprintf(F,"> ");
201          if (Comp == 0)
202             fprintf(F,"= ");
203          if (Comp > 0)
204             fprintf(F,"< ");
205          fprintf(F,"%s ",S.InstVerIter(Cache).VerStr());
206       }
207       else
208          fprintf(F,"> - ");
209       
210       // Show the filename/operation
211       if (I->Op == Item::Install)
212       {
213          // No errors here..
214          if (I->File[0] != '/')
215             fprintf(F,"**ERROR**\n");
216          else
217             fprintf(F,"%s\n",I->File.c_str());
218       }      
219       if (I->Op == Item::Configure)
220          fprintf(F,"**CONFIGURE**\n");
221       if (I->Op == Item::Remove ||
222           I->Op == Item::Purge)
223          fprintf(F,"**REMOVE**\n");
224       
225       if (ferror(F) != 0)
226          return false;
227    }
228    return true;
229 }
230                                                                         /*}}}*/
231 // DPkgPM::RunScriptsWithPkgs - Run scripts with package names on stdin /*{{{*/
232 // ---------------------------------------------------------------------
233 /* This looks for a list of scripts to run from the configuration file
234    each one is run and is fed on standard input a list of all .deb files
235    that are due to be installed. */
236 bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
237 {
238    Configuration::Item const *Opts = _config->Tree(Cnf);
239    if (Opts == 0 || Opts->Child == 0)
240       return true;
241    Opts = Opts->Child;
242    
243    unsigned int Count = 1;
244    for (; Opts != 0; Opts = Opts->Next, Count++)
245    {
246       if (Opts->Value.empty() == true)
247          continue;
248
249       // Determine the protocol version
250       string OptSec = Opts->Value;
251       string::size_type Pos;
252       if ((Pos = OptSec.find(' ')) == string::npos || Pos == 0)
253          Pos = OptSec.length();
254       OptSec = "DPkg::Tools::Options::" + string(Opts->Value.c_str(),Pos);
255       
256       unsigned int Version = _config->FindI(OptSec+"::Version",1);
257       
258       // Create the pipes
259       int Pipes[2];
260       if (pipe(Pipes) != 0)
261          return _error->Errno("pipe","Failed to create IPC pipe to subprocess");
262       SetCloseExec(Pipes[0],true);
263       SetCloseExec(Pipes[1],true);
264       
265       // Purified Fork for running the script
266       pid_t Process = ExecFork();      
267       if (Process == 0)
268       {
269          // Setup the FDs
270          dup2(Pipes[0],STDIN_FILENO);
271          SetCloseExec(STDOUT_FILENO,false);
272          SetCloseExec(STDIN_FILENO,false);      
273          SetCloseExec(STDERR_FILENO,false);
274
275          const char *Args[4];
276          Args[0] = "/bin/sh";
277          Args[1] = "-c";
278          Args[2] = Opts->Value.c_str();
279          Args[3] = 0;
280          execv(Args[0],(char **)Args);
281          _exit(100);
282       }
283       close(Pipes[0]);
284       FILE *F = fdopen(Pipes[1],"w");
285       if (F == 0)
286          return _error->Errno("fdopen","Faild to open new FD");
287       
288       // Feed it the filenames.
289       bool Die = false;
290       if (Version <= 1)
291       {
292          for (vector<Item>::iterator I = List.begin(); I != List.end(); I++)
293          {
294             // Only deal with packages to be installed from .deb
295             if (I->Op != Item::Install)
296                continue;
297
298             // No errors here..
299             if (I->File[0] != '/')
300                continue;
301             
302             /* Feed the filename of each package that is pending install
303                into the pipe. */
304             fprintf(F,"%s\n",I->File.c_str());
305             if (ferror(F) != 0)
306             {
307                Die = true;
308                break;
309             }
310          }
311       }
312       else
313          Die = !SendV2Pkgs(F);
314
315       fclose(F);
316       
317       // Clean up the sub process
318       if (ExecWait(Process,Opts->Value.c_str()) == false)
319          return _error->Error("Failure running script %s",Opts->Value.c_str());
320    }
321
322    return true;
323 }
324                                                                         /*}}}*/
325 // DPkgPM::Go - Run the sequence                                        /*{{{*/
326 // ---------------------------------------------------------------------
327 /* This globs the operations and calls dpkg */
328 bool pkgDPkgPM::Go()
329 {
330    unsigned int MaxArgs = _config->FindI("Dpkg::MaxArgs",350);   
331    unsigned int MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes",8192);
332
333    if (RunScripts("DPkg::Pre-Invoke") == false)
334       return false;
335
336    if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false)
337       return false;
338
339    for (vector<Item>::iterator I = List.begin(); I != List.end();)
340    {
341       vector<Item>::iterator J = I;
342       for (; J != List.end() && J->Op == I->Op; J++);
343
344       // Generate the argument list
345       const char *Args[MaxArgs + 50];
346       if (J - I > (signed)MaxArgs)
347          J = I + MaxArgs;
348       
349       unsigned int n = 0;
350       unsigned long Size = 0;
351       string Tmp = _config->Find("Dir::Bin::dpkg","dpkg");
352       Args[n++] = Tmp.c_str();
353       Size += strlen(Args[n-1]);
354       
355       // Stick in any custom dpkg options
356       Configuration::Item const *Opts = _config->Tree("DPkg::Options");
357       if (Opts != 0)
358       {
359          Opts = Opts->Child;
360          for (; Opts != 0; Opts = Opts->Next)
361          {
362             if (Opts->Value.empty() == true)
363                continue;
364             Args[n++] = Opts->Value.c_str();
365             Size += Opts->Value.length();
366          }       
367       }
368       
369       switch (I->Op)
370       {
371          case Item::Remove:
372          Args[n++] = "--force-depends";
373          Size += strlen(Args[n-1]);
374          Args[n++] = "--force-remove-essential";
375          Size += strlen(Args[n-1]);
376          Args[n++] = "--remove";
377          Size += strlen(Args[n-1]);
378          break;
379          
380          case Item::Purge:
381          Args[n++] = "--force-depends";
382          Size += strlen(Args[n-1]);
383          Args[n++] = "--force-remove-essential";
384          Size += strlen(Args[n-1]);
385          Args[n++] = "--purge";
386          Size += strlen(Args[n-1]);
387          break;
388          
389          case Item::Configure:
390          Args[n++] = "--configure";
391          Size += strlen(Args[n-1]);
392          break;
393          
394          case Item::Install:
395          Args[n++] = "--unpack";
396          Size += strlen(Args[n-1]);
397          break;
398       }
399       
400       // Write in the file or package names
401       if (I->Op == Item::Install)
402       {
403          for (;I != J && Size < MaxArgBytes; I++)
404          {
405             if (I->File[0] != '/')
406                return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
407             Args[n++] = I->File.c_str();
408             Size += strlen(Args[n-1]);
409          }
410       }      
411       else
412       {
413          for (;I != J && Size < MaxArgBytes; I++)
414          {
415             Args[n++] = I->Pkg.Name();
416             Size += strlen(Args[n-1]);
417          }       
418       }      
419       Args[n] = 0;
420       J = I;
421       
422       if (_config->FindB("Debug::pkgDPkgPM",false) == true)
423       {
424          for (unsigned int k = 0; k != n; k++)
425             clog << Args[k] << ' ';
426          clog << endl;
427          continue;
428       }
429       
430       cout << flush;
431       clog << flush;
432       cerr << flush;
433
434       /* Mask off sig int/quit. We do this because dpkg also does when 
435          it forks scripts. What happens is that when you hit ctrl-c it sends
436          it to all processes in the group. Since dpkg ignores the signal 
437          it doesn't die but we do! So we must also ignore it */
438       signal(SIGQUIT,SIG_IGN);
439       signal(SIGINT,SIG_IGN);
440                      
441       // Fork dpkg
442       pid_t Child = ExecFork();
443             
444       // This is the child
445       if (Child == 0)
446       {
447          if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0)
448             _exit(100);
449          
450          if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO))
451          {
452             int Flags,dummy;
453             if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0)
454                _exit(100);
455             
456             // Discard everything in stdin before forking dpkg
457             if (fcntl(STDIN_FILENO,F_SETFL,Flags | O_NONBLOCK) < 0)
458                _exit(100);
459             
460             while (read(STDIN_FILENO,&dummy,1) == 1);
461             
462             if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0)
463                _exit(100);
464          }
465          
466          /* No Job Control Stop Env is a magic dpkg var that prevents it
467             from using sigstop */
468          putenv("DPKG_NO_TSTP=yes");
469          execvp(Args[0],(char **)Args);
470          cerr << "Could not exec dpkg!" << endl;
471          _exit(100);
472       }      
473
474       // Wait for dpkg
475       int Status = 0;
476       while (waitpid(Child,&Status,0) != Child)
477       {
478          if (errno == EINTR)
479             continue;
480          RunScripts("DPkg::Post-Invoke");
481          return _error->Errno("waitpid","Couldn't wait for subprocess");
482       }
483
484       // Restore sig int/quit
485       signal(SIGQUIT,SIG_DFL);
486       signal(SIGINT,SIG_DFL);
487        
488       // Check for an error code.
489       if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
490       {
491          RunScripts("DPkg::Post-Invoke");
492          if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV)
493             return _error->Error("Sub-process %s received a segmentation fault.",Args[0]);
494
495          if (WIFEXITED(Status) != 0)
496             return _error->Error("Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status));
497          
498          return _error->Error("Sub-process %s exited unexpectedly",Args[0]);
499       }      
500    }
501
502    if (RunScripts("DPkg::Post-Invoke") == false)
503       return false;
504    return true;
505 }
506                                                                         /*}}}*/
507 // pkgDpkgPM::Reset - Dump the contents of the command list             /*{{{*/
508 // ---------------------------------------------------------------------
509 /* */
510 void pkgDPkgPM::Reset() 
511 {
512    List.erase(List.begin(),List.end());
513 }
514                                                                         /*}}}*/