]> git.saurik.com Git - apt.git/blob - apt-pkg/deb/dpkgpm.cc
* doc/examples/configure-index:
[apt.git] / apt-pkg / deb / dpkgpm.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: dpkgpm.cc,v 1.28 2004/01/27 02:25:01 mdz Exp $
4 /* ######################################################################
5
6 DPKG Package Manager - Provide an interface to dpkg
7
8 ##################################################################### */
9 /*}}}*/
10 // Includes /*{{{*/
11 #ifdef __GNUG__
12 #pragma implementation "apt-pkg/dpkgpm.h"
13 #endif
14 #include <apt-pkg/dpkgpm.h>
15 #include <apt-pkg/error.h>
16 #include <apt-pkg/configuration.h>
17 #include <apt-pkg/depcache.h>
18 #include <apt-pkg/strutl.h>
19 #include <apt-pkg/fileutl.h>
20
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include <fcntl.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <signal.h>
27 #include <errno.h>
28 #include <stdio.h>
29 #include <sstream>
30 #include <map>
31
32 #include <config.h>
33 #include <apti18n.h>
34 /*}}}*/
35
36 using namespace std;
37
38 // DPkgPM::pkgDPkgPM - Constructor /*{{{*/
39 // ---------------------------------------------------------------------
40 /* */
41 pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) : pkgPackageManager(Cache)
42 {
43 }
44 /*}}}*/
45 // DPkgPM::pkgDPkgPM - Destructor /*{{{*/
46 // ---------------------------------------------------------------------
47 /* */
48 pkgDPkgPM::~pkgDPkgPM()
49 {
50 }
51 /*}}}*/
52 // DPkgPM::Install - Install a package /*{{{*/
53 // ---------------------------------------------------------------------
54 /* Add an install operation to the sequence list */
55 bool pkgDPkgPM::Install(PkgIterator Pkg,string File)
56 {
57 if (File.empty() == true || Pkg.end() == true)
58 return _error->Error("Internal Error, No file name for %s",Pkg.Name());
59
60 List.push_back(Item(Item::Install,Pkg,File));
61 return true;
62 }
63 /*}}}*/
64 // DPkgPM::Configure - Configure a package /*{{{*/
65 // ---------------------------------------------------------------------
66 /* Add a configure operation to the sequence list */
67 bool pkgDPkgPM::Configure(PkgIterator Pkg)
68 {
69 if (Pkg.end() == true)
70 return false;
71
72 List.push_back(Item(Item::Configure,Pkg));
73 return true;
74 }
75 /*}}}*/
76 // DPkgPM::Remove - Remove a package /*{{{*/
77 // ---------------------------------------------------------------------
78 /* Add a remove operation to the sequence list */
79 bool pkgDPkgPM::Remove(PkgIterator Pkg,bool Purge)
80 {
81 if (Pkg.end() == true)
82 return false;
83
84 if (Purge == true)
85 List.push_back(Item(Item::Purge,Pkg));
86 else
87 List.push_back(Item(Item::Remove,Pkg));
88 return true;
89 }
90 /*}}}*/
91 // DPkgPM::RunScripts - Run a set of scripts /*{{{*/
92 // ---------------------------------------------------------------------
93 /* This looks for a list of script sto run from the configuration file,
94 each one is run with system from a forked child. */
95 bool pkgDPkgPM::RunScripts(const char *Cnf)
96 {
97 RunScripts(Cnf);
98 }
99 /*}}}*/
100 // DPkgPM::SendV2Pkgs - Send version 2 package info /*{{{*/
101 // ---------------------------------------------------------------------
102 /* This is part of the helper script communication interface, it sends
103 very complete information down to the other end of the pipe.*/
104 bool pkgDPkgPM::SendV2Pkgs(FILE *F)
105 {
106 fprintf(F,"VERSION 2\n");
107
108 /* Write out all of the configuration directives by walking the
109 configuration tree */
110 const Configuration::Item *Top = _config->Tree(0);
111 for (; Top != 0;)
112 {
113 if (Top->Value.empty() == false)
114 {
115 fprintf(F,"%s=%s\n",
116 QuoteString(Top->FullTag(),"=\"\n").c_str(),
117 QuoteString(Top->Value,"\n").c_str());
118 }
119
120 if (Top->Child != 0)
121 {
122 Top = Top->Child;
123 continue;
124 }
125
126 while (Top != 0 && Top->Next == 0)
127 Top = Top->Parent;
128 if (Top != 0)
129 Top = Top->Next;
130 }
131 fprintf(F,"\n");
132
133 // Write out the package actions in order.
134 for (vector<Item>::iterator I = List.begin(); I != List.end(); I++)
135 {
136 pkgDepCache::StateCache &S = Cache[I->Pkg];
137
138 fprintf(F,"%s ",I->Pkg.Name());
139 // Current version
140 if (I->Pkg->CurrentVer == 0)
141 fprintf(F,"- ");
142 else
143 fprintf(F,"%s ",I->Pkg.CurrentVer().VerStr());
144
145 // Show the compare operator
146 // Target version
147 if (S.InstallVer != 0)
148 {
149 int Comp = 2;
150 if (I->Pkg->CurrentVer != 0)
151 Comp = S.InstVerIter(Cache).CompareVer(I->Pkg.CurrentVer());
152 if (Comp < 0)
153 fprintf(F,"> ");
154 if (Comp == 0)
155 fprintf(F,"= ");
156 if (Comp > 0)
157 fprintf(F,"< ");
158 fprintf(F,"%s ",S.InstVerIter(Cache).VerStr());
159 }
160 else
161 fprintf(F,"> - ");
162
163 // Show the filename/operation
164 if (I->Op == Item::Install)
165 {
166 // No errors here..
167 if (I->File[0] != '/')
168 fprintf(F,"**ERROR**\n");
169 else
170 fprintf(F,"%s\n",I->File.c_str());
171 }
172 if (I->Op == Item::Configure)
173 fprintf(F,"**CONFIGURE**\n");
174 if (I->Op == Item::Remove ||
175 I->Op == Item::Purge)
176 fprintf(F,"**REMOVE**\n");
177
178 if (ferror(F) != 0)
179 return false;
180 }
181 return true;
182 }
183 /*}}}*/
184 // DPkgPM::RunScriptsWithPkgs - Run scripts with package names on stdin /*{{{*/
185 // ---------------------------------------------------------------------
186 /* This looks for a list of scripts to run from the configuration file
187 each one is run and is fed on standard input a list of all .deb files
188 that are due to be installed. */
189 bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
190 {
191 Configuration::Item const *Opts = _config->Tree(Cnf);
192 if (Opts == 0 || Opts->Child == 0)
193 return true;
194 Opts = Opts->Child;
195
196 unsigned int Count = 1;
197 for (; Opts != 0; Opts = Opts->Next, Count++)
198 {
199 if (Opts->Value.empty() == true)
200 continue;
201
202 // Determine the protocol version
203 string OptSec = Opts->Value;
204 string::size_type Pos;
205 if ((Pos = OptSec.find(' ')) == string::npos || Pos == 0)
206 Pos = OptSec.length();
207 OptSec = "DPkg::Tools::Options::" + string(Opts->Value.c_str(),Pos);
208
209 unsigned int Version = _config->FindI(OptSec+"::Version",1);
210
211 // Create the pipes
212 int Pipes[2];
213 if (pipe(Pipes) != 0)
214 return _error->Errno("pipe","Failed to create IPC pipe to subprocess");
215 SetCloseExec(Pipes[0],true);
216 SetCloseExec(Pipes[1],true);
217
218 // Purified Fork for running the script
219 pid_t Process = ExecFork();
220 if (Process == 0)
221 {
222 // Setup the FDs
223 dup2(Pipes[0],STDIN_FILENO);
224 SetCloseExec(STDOUT_FILENO,false);
225 SetCloseExec(STDIN_FILENO,false);
226 SetCloseExec(STDERR_FILENO,false);
227
228 const char *Args[4];
229 Args[0] = "/bin/sh";
230 Args[1] = "-c";
231 Args[2] = Opts->Value.c_str();
232 Args[3] = 0;
233 execv(Args[0],(char **)Args);
234 _exit(100);
235 }
236 close(Pipes[0]);
237 FILE *F = fdopen(Pipes[1],"w");
238 if (F == 0)
239 return _error->Errno("fdopen","Faild to open new FD");
240
241 // Feed it the filenames.
242 bool Die = false;
243 if (Version <= 1)
244 {
245 for (vector<Item>::iterator I = List.begin(); I != List.end(); I++)
246 {
247 // Only deal with packages to be installed from .deb
248 if (I->Op != Item::Install)
249 continue;
250
251 // No errors here..
252 if (I->File[0] != '/')
253 continue;
254
255 /* Feed the filename of each package that is pending install
256 into the pipe. */
257 fprintf(F,"%s\n",I->File.c_str());
258 if (ferror(F) != 0)
259 {
260 Die = true;
261 break;
262 }
263 }
264 }
265 else
266 Die = !SendV2Pkgs(F);
267
268 fclose(F);
269
270 // Clean up the sub process
271 if (ExecWait(Process,Opts->Value.c_str()) == false)
272 return _error->Error("Failure running script %s",Opts->Value.c_str());
273 }
274
275 return true;
276 }
277 /*}}}*/
278 // DPkgPM::Go - Run the sequence /*{{{*/
279 // ---------------------------------------------------------------------
280 /* This globs the operations and calls dpkg
281 *
282 * If it is called with "OutStatusFd" set to a valid file descriptor
283 * apt will report the install progress over this fd. It maps the
284 * dpkg states a package goes through to human readable (and i10n-able)
285 * names and calculates a percentage for each step.
286 */
287 bool pkgDPkgPM::Go(int OutStatusFd)
288 {
289 unsigned int MaxArgs = _config->FindI("Dpkg::MaxArgs",8*1024);
290 unsigned int MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes",32*1024);
291
292 if (RunScripts("DPkg::Pre-Invoke") == false)
293 return false;
294
295 if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false)
296 return false;
297
298 // prepare the progress reporting
299 int Done = 0;
300 int Total = 0;
301 // map the dpkg states to the operations that are performed
302 // (this is sorted in the same way as Item::Ops)
303 static const struct DpkgState DpkgStatesOpMap[][5] = {
304 // Install operation
305 {
306 {"half-installed", _("Preparing %s")},
307 {"unpacked", _("Unpacking %s") },
308 {NULL, NULL}
309 },
310 // Configure operation
311 {
312 {"unpacked",_("Preparing to configure %s") },
313 {"half-configured", _("Configuring %s") },
314 { "installed", _("Installed %s")},
315 {NULL, NULL}
316 },
317 // Remove operation
318 {
319 {"half-configured", _("Preparing for removal of %s")},
320 {"half-installed", _("Removing %s")},
321 {"config-files", _("Removed %s")},
322 {NULL, NULL}
323 },
324 // Purge operation
325 {
326 {"config-files", _("Preparing to completely remove %s")},
327 {"not-installed", _("Completely removed %s")},
328 {NULL, NULL}
329 },
330 };
331
332 // the dpkg states that the pkg will run through, the string is
333 // the package, the vector contains the dpkg states that the package
334 // will go through
335 map<string,vector<struct DpkgState> > PackageOps;
336 // the dpkg states that are already done; the string is the package
337 // the int is the state that is already done (e.g. a package that is
338 // going to be install is already in state "half-installed")
339 map<string,int> PackageOpsDone;
340
341 // init the PackageOps map, go over the list of packages that
342 // that will be [installed|configured|removed|purged] and add
343 // them to the PackageOps map (the dpkg states it goes through)
344 // and the PackageOpsTranslations (human readable strings)
345 for (vector<Item>::iterator I = List.begin(); I != List.end();I++)
346 {
347 string name = (*I).Pkg.Name();
348 PackageOpsDone[name] = 0;
349 for(int i=0; (DpkgStatesOpMap[(*I).Op][i]).state != NULL; i++)
350 {
351 PackageOps[name].push_back(DpkgStatesOpMap[(*I).Op][i]);
352 Total++;
353 }
354 }
355
356 // this loop is runs once per operation
357 for (vector<Item>::iterator I = List.begin(); I != List.end();)
358 {
359 vector<Item>::iterator J = I;
360 for (; J != List.end() && J->Op == I->Op; J++);
361
362 // Generate the argument list
363 const char *Args[MaxArgs + 50];
364 if (J - I > (signed)MaxArgs)
365 J = I + MaxArgs;
366
367 unsigned int n = 0;
368 unsigned long Size = 0;
369 string Tmp = _config->Find("Dir::Bin::dpkg","dpkg");
370 Args[n++] = Tmp.c_str();
371 Size += strlen(Args[n-1]);
372
373 // Stick in any custom dpkg options
374 Configuration::Item const *Opts = _config->Tree("DPkg::Options");
375 if (Opts != 0)
376 {
377 Opts = Opts->Child;
378 for (; Opts != 0; Opts = Opts->Next)
379 {
380 if (Opts->Value.empty() == true)
381 continue;
382 Args[n++] = Opts->Value.c_str();
383 Size += Opts->Value.length();
384 }
385 }
386
387 char status_fd_buf[20];
388 int fd[2];
389 pipe(fd);
390
391 Args[n++] = "--status-fd";
392 Size += strlen(Args[n-1]);
393 snprintf(status_fd_buf,sizeof(status_fd_buf),"%i", fd[1]);
394 Args[n++] = status_fd_buf;
395 Size += strlen(Args[n-1]);
396
397 switch (I->Op)
398 {
399 case Item::Remove:
400 Args[n++] = "--force-depends";
401 Size += strlen(Args[n-1]);
402 Args[n++] = "--force-remove-essential";
403 Size += strlen(Args[n-1]);
404 Args[n++] = "--remove";
405 Size += strlen(Args[n-1]);
406 break;
407
408 case Item::Purge:
409 Args[n++] = "--force-depends";
410 Size += strlen(Args[n-1]);
411 Args[n++] = "--force-remove-essential";
412 Size += strlen(Args[n-1]);
413 Args[n++] = "--purge";
414 Size += strlen(Args[n-1]);
415 break;
416
417 case Item::Configure:
418 Args[n++] = "--configure";
419 Size += strlen(Args[n-1]);
420 break;
421
422 case Item::Install:
423 Args[n++] = "--unpack";
424 Size += strlen(Args[n-1]);
425 break;
426 }
427
428 // Write in the file or package names
429 if (I->Op == Item::Install)
430 {
431 for (;I != J && Size < MaxArgBytes; I++)
432 {
433 if (I->File[0] != '/')
434 return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
435 Args[n++] = I->File.c_str();
436 Size += strlen(Args[n-1]);
437 }
438 }
439 else
440 {
441 for (;I != J && Size < MaxArgBytes; I++)
442 {
443 Args[n++] = I->Pkg.Name();
444 Size += strlen(Args[n-1]);
445 }
446 }
447 Args[n] = 0;
448 J = I;
449
450 if (_config->FindB("Debug::pkgDPkgPM",false) == true)
451 {
452 for (unsigned int k = 0; k != n; k++)
453 clog << Args[k] << ' ';
454 clog << endl;
455 continue;
456 }
457
458 cout << flush;
459 clog << flush;
460 cerr << flush;
461
462 /* Mask off sig int/quit. We do this because dpkg also does when
463 it forks scripts. What happens is that when you hit ctrl-c it sends
464 it to all processes in the group. Since dpkg ignores the signal
465 it doesn't die but we do! So we must also ignore it */
466 sighandler_t old_SIGQUIT = signal(SIGQUIT,SIG_IGN);
467 sighandler_t old_SIGINT = signal(SIGINT,SIG_IGN);
468
469 // Fork dpkg
470 pid_t Child;
471 _config->Set("APT::Keep-Fds::",fd[1]);
472 Child = ExecFork();
473
474 // This is the child
475 if (Child == 0)
476 {
477 close(fd[0]); // close the read end of the pipe
478
479 if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0)
480 _exit(100);
481
482 if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO))
483 {
484 int Flags,dummy;
485 if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0)
486 _exit(100);
487
488 // Discard everything in stdin before forking dpkg
489 if (fcntl(STDIN_FILENO,F_SETFL,Flags | O_NONBLOCK) < 0)
490 _exit(100);
491
492 while (read(STDIN_FILENO,&dummy,1) == 1);
493
494 if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0)
495 _exit(100);
496 }
497
498 /* No Job Control Stop Env is a magic dpkg var that prevents it
499 from using sigstop */
500 putenv("DPKG_NO_TSTP=yes");
501 execvp(Args[0],(char **)Args);
502 cerr << "Could not exec dpkg!" << endl;
503 _exit(100);
504 }
505
506 // clear the Keep-Fd again
507 _config->Clear("APT::Keep-Fds",fd[1]);
508
509 // Wait for dpkg
510 int Status = 0;
511
512 // we read from dpkg here
513 int _dpkgin = fd[0];
514 fcntl(_dpkgin, F_SETFL, O_NONBLOCK);
515 close(fd[1]); // close the write end of the pipe
516
517 // the read buffers for the communication with dpkg
518 char line[1024] = {0,};
519 char buf[2] = {0,0};
520
521 // the result of the waitpid call
522 int res;
523
524 while ((res=waitpid(Child,&Status, WNOHANG)) != Child) {
525 if(res < 0) {
526 // FIXME: move this to a function or something, looks ugly here
527 // error handling, waitpid returned -1
528 if (errno == EINTR)
529 continue;
530 RunScripts("DPkg::Post-Invoke");
531
532 // Restore sig int/quit
533 signal(SIGQUIT,old_SIGQUIT);
534 signal(SIGINT,old_SIGINT);
535 return _error->Errno("waitpid","Couldn't wait for subprocess");
536 }
537
538 // read a single char, make sure that the read can't block
539 // (otherwise we may leave zombies)
540 int len = read(_dpkgin, buf, 1);
541
542 // nothing to read, wait a bit for more
543 if(len <= 0)
544 {
545 usleep(1000);
546 continue;
547 }
548
549 // sanity check (should never happen)
550 if(strlen(line) >= sizeof(line)-10)
551 {
552 _error->Error("got a overlong line from dpkg: '%s'",line);
553 line[0]=0;
554 }
555 // append to line, check if we got a complete line
556 strcat(line, buf);
557 if(buf[0] != '\n')
558 continue;
559
560 if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true)
561 std::clog << "got from dpkg '" << line << "'" << std::endl;
562
563 // the status we output
564 ostringstream status;
565
566 /* dpkg sends strings like this:
567 'status: <pkg>: <pkg qstate>'
568 errors look like this:
569 'status: /var/cache/apt/archives/krecipes_0.8.1-0ubuntu1_i386.deb : error : trying to overwrite `/usr/share/doc/kde/HTML/en/krecipes/krectip.png', which is also in package krecipes-data
570 and conffile-prompt like this
571 'status: conffile-prompt: conffile : 'current-conffile' 'new-conffile' useredited distedited
572
573 */
574 char* list[5];
575 TokSplitString(':', line, list, sizeof(list)/sizeof(list[0]));
576 char *pkg = list[1];
577 char *action = _strstrip(list[2]);
578
579 if(strncmp(action,"error",strlen("error")) == 0)
580 {
581 status << "pmerror:" << list[1]
582 << ":" << (Done/float(Total)*100.0)
583 << ":" << list[3]
584 << endl;
585 if(OutStatusFd > 0)
586 write(OutStatusFd, status.str().c_str(), status.str().size());
587 line[0]=0;
588 if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true)
589 std::clog << "send: '" << status.str() << "'" << endl;
590 continue;
591 }
592 if(strncmp(action,"conffile",strlen("conffile")) == 0)
593 {
594 status << "pmconffile:" << list[1]
595 << ":" << (Done/float(Total)*100.0)
596 << ":" << list[3]
597 << endl;
598 if(OutStatusFd > 0)
599 write(OutStatusFd, status.str().c_str(), status.str().size());
600 line[0]=0;
601 if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true)
602 std::clog << "send: '" << status.str() << "'" << endl;
603 continue;
604 }
605
606 vector<struct DpkgState> &states = PackageOps[pkg];
607 const char *next_action = NULL;
608 if(PackageOpsDone[pkg] < states.size())
609 next_action = states[PackageOpsDone[pkg]].state;
610 // check if the package moved to the next dpkg state
611 if(next_action && (strcmp(action, next_action) == 0))
612 {
613 // only read the translation if there is actually a next
614 // action
615 const char *translation = states[PackageOpsDone[pkg]].str;
616 char s[200];
617 snprintf(s, sizeof(s), translation, pkg);
618
619 // we moved from one dpkg state to a new one, report that
620 PackageOpsDone[pkg]++;
621 Done++;
622 // build the status str
623 status << "pmstatus:" << pkg
624 << ":" << (Done/float(Total)*100.0)
625 << ":" << s
626 << endl;
627 if(OutStatusFd > 0)
628 write(OutStatusFd, status.str().c_str(), status.str().size());
629 if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true)
630 std::clog << "send: '" << status.str() << "'" << endl;
631
632 }
633 if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true)
634 std::clog << "(parsed from dpkg) pkg: " << pkg
635 << " action: " << action << endl;
636
637 // reset the line buffer
638 line[0]=0;
639 }
640 close(_dpkgin);
641
642 // Restore sig int/quit
643 signal(SIGQUIT,old_SIGQUIT);
644 signal(SIGINT,old_SIGINT);
645
646 // Check for an error code.
647 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
648 {
649 RunScripts("DPkg::Post-Invoke");
650 if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV)
651 return _error->Error("Sub-process %s received a segmentation fault.",Args[0]);
652
653 if (WIFEXITED(Status) != 0)
654 return _error->Error("Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status));
655
656 return _error->Error("Sub-process %s exited unexpectedly",Args[0]);
657 }
658 }
659
660 if (RunScripts("DPkg::Post-Invoke") == false)
661 return false;
662 return true;
663 }
664 /*}}}*/
665 // pkgDpkgPM::Reset - Dump the contents of the command list /*{{{*/
666 // ---------------------------------------------------------------------
667 /* */
668 void pkgDPkgPM::Reset()
669 {
670 List.erase(List.begin(),List.end());
671 }
672 /*}}}*/