1 // -*- mode: cpp; mode: fold -*-
3 // $Id: dpkgpm.cc,v 1.28 2004/01/27 02:25:01 mdz Exp $
4 /* ######################################################################
6 DPKG Package Manager - Provide an interface to dpkg
8 ##################################################################### */
11 #include <apt-pkg/dpkgpm.h>
12 #include <apt-pkg/error.h>
13 #include <apt-pkg/configuration.h>
14 #include <apt-pkg/depcache.h>
15 #include <apt-pkg/strutl.h>
20 #include <sys/types.h>
34 // DPkgPM::pkgDPkgPM - Constructor /*{{{*/
35 // ---------------------------------------------------------------------
37 pkgDPkgPM::pkgDPkgPM(pkgDepCache
*Cache
) : pkgPackageManager(Cache
)
41 // DPkgPM::pkgDPkgPM - Destructor /*{{{*/
42 // ---------------------------------------------------------------------
44 pkgDPkgPM::~pkgDPkgPM()
48 // DPkgPM::Install - Install a package /*{{{*/
49 // ---------------------------------------------------------------------
50 /* Add an install operation to the sequence list */
51 bool pkgDPkgPM::Install(PkgIterator Pkg
,string File
)
53 if (File
.empty() == true || Pkg
.end() == true)
54 return _error
->Error("Internal Error, No file name for %s",Pkg
.Name());
56 List
.push_back(Item(Item::Install
,Pkg
,File
));
60 // DPkgPM::Configure - Configure a package /*{{{*/
61 // ---------------------------------------------------------------------
62 /* Add a configure operation to the sequence list */
63 bool pkgDPkgPM::Configure(PkgIterator Pkg
)
65 if (Pkg
.end() == true)
68 List
.push_back(Item(Item::Configure
,Pkg
));
72 // DPkgPM::Remove - Remove a package /*{{{*/
73 // ---------------------------------------------------------------------
74 /* Add a remove operation to the sequence list */
75 bool pkgDPkgPM::Remove(PkgIterator Pkg
,bool Purge
)
77 if (Pkg
.end() == true)
81 List
.push_back(Item(Item::Purge
,Pkg
));
83 List
.push_back(Item(Item::Remove
,Pkg
));
87 // DPkgPM::RunScripts - Run a set of scripts /*{{{*/
88 // ---------------------------------------------------------------------
89 /* This looks for a list of script sto run from the configuration file,
90 each one is run with system from a forked child. */
91 bool pkgDPkgPM::RunScripts(const char *Cnf
)
93 Configuration::Item
const *Opts
= _config
->Tree(Cnf
);
94 if (Opts
== 0 || Opts
->Child
== 0)
98 // Fork for running the system calls
99 pid_t Child
= ExecFork();
104 if (chdir("/tmp/") != 0)
107 unsigned int Count
= 1;
108 for (; Opts
!= 0; Opts
= Opts
->Next
, Count
++)
110 if (Opts
->Value
.empty() == true)
113 if (system(Opts
->Value
.c_str()) != 0)
119 // Wait for the child
121 while (waitpid(Child
,&Status
,0) != Child
)
125 return _error
->Errno("waitpid","Couldn't wait for subprocess");
128 // Restore sig int/quit
129 signal(SIGQUIT
,SIG_DFL
);
130 signal(SIGINT
,SIG_DFL
);
132 // Check for an error code.
133 if (WIFEXITED(Status
) == 0 || WEXITSTATUS(Status
) != 0)
135 unsigned int Count
= WEXITSTATUS(Status
);
139 for (; Opts
!= 0 && Count
!= 1; Opts
= Opts
->Next
, Count
--);
140 _error
->Error("Problem executing scripts %s '%s'",Cnf
,Opts
->Value
.c_str());
143 return _error
->Error("Sub-process returned an error code");
149 // DPkgPM::SendV2Pkgs - Send version 2 package info /*{{{*/
150 // ---------------------------------------------------------------------
151 /* This is part of the helper script communication interface, it sends
152 very complete information down to the other end of the pipe.*/
153 bool pkgDPkgPM::SendV2Pkgs(FILE *F
)
155 fprintf(F
,"VERSION 2\n");
157 /* Write out all of the configuration directives by walking the
158 configuration tree */
159 const Configuration::Item
*Top
= _config
->Tree(0);
162 if (Top
->Value
.empty() == false)
165 QuoteString(Top
->FullTag(),"=\"\n").c_str(),
166 QuoteString(Top
->Value
,"\n").c_str());
175 while (Top
!= 0 && Top
->Next
== 0)
182 // Write out the package actions in order.
183 for (vector
<Item
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
185 pkgDepCache::StateCache
&S
= Cache
[I
->Pkg
];
187 fprintf(F
,"%s ",I
->Pkg
.Name());
189 if (I
->Pkg
->CurrentVer
== 0)
192 fprintf(F
,"%s ",I
->Pkg
.CurrentVer().VerStr());
194 // Show the compare operator
196 if (S
.InstallVer
!= 0)
199 if (I
->Pkg
->CurrentVer
!= 0)
200 Comp
= S
.InstVerIter(Cache
).CompareVer(I
->Pkg
.CurrentVer());
207 fprintf(F
,"%s ",S
.InstVerIter(Cache
).VerStr());
212 // Show the filename/operation
213 if (I
->Op
== Item::Install
)
216 if (I
->File
[0] != '/')
217 fprintf(F
,"**ERROR**\n");
219 fprintf(F
,"%s\n",I
->File
.c_str());
221 if (I
->Op
== Item::Configure
)
222 fprintf(F
,"**CONFIGURE**\n");
223 if (I
->Op
== Item::Remove
||
224 I
->Op
== Item::Purge
)
225 fprintf(F
,"**REMOVE**\n");
233 // DPkgPM::RunScriptsWithPkgs - Run scripts with package names on stdin /*{{{*/
234 // ---------------------------------------------------------------------
235 /* This looks for a list of scripts to run from the configuration file
236 each one is run and is fed on standard input a list of all .deb files
237 that are due to be installed. */
238 bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf
)
240 Configuration::Item
const *Opts
= _config
->Tree(Cnf
);
241 if (Opts
== 0 || Opts
->Child
== 0)
245 unsigned int Count
= 1;
246 for (; Opts
!= 0; Opts
= Opts
->Next
, Count
++)
248 if (Opts
->Value
.empty() == true)
251 // Determine the protocol version
252 string OptSec
= Opts
->Value
;
253 string::size_type Pos
;
254 if ((Pos
= OptSec
.find(' ')) == string::npos
|| Pos
== 0)
255 Pos
= OptSec
.length();
256 OptSec
= "DPkg::Tools::Options::" + string(Opts
->Value
.c_str(),Pos
);
258 unsigned int Version
= _config
->FindI(OptSec
+"::Version",1);
262 if (pipe(Pipes
) != 0)
263 return _error
->Errno("pipe","Failed to create IPC pipe to subprocess");
264 SetCloseExec(Pipes
[0],true);
265 SetCloseExec(Pipes
[1],true);
267 // Purified Fork for running the script
268 pid_t Process
= ExecFork();
272 dup2(Pipes
[0],STDIN_FILENO
);
273 SetCloseExec(STDOUT_FILENO
,false);
274 SetCloseExec(STDIN_FILENO
,false);
275 SetCloseExec(STDERR_FILENO
,false);
280 Args
[2] = Opts
->Value
.c_str();
282 execv(Args
[0],(char **)Args
);
286 FILE *F
= fdopen(Pipes
[1],"w");
288 return _error
->Errno("fdopen","Faild to open new FD");
290 // Feed it the filenames.
294 for (vector
<Item
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
296 // Only deal with packages to be installed from .deb
297 if (I
->Op
!= Item::Install
)
301 if (I
->File
[0] != '/')
304 /* Feed the filename of each package that is pending install
306 fprintf(F
,"%s\n",I
->File
.c_str());
315 Die
= !SendV2Pkgs(F
);
319 // Clean up the sub process
320 if (ExecWait(Process
,Opts
->Value
.c_str()) == false)
321 return _error
->Error("Failure running script %s",Opts
->Value
.c_str());
327 // DPkgPM::Go - Run the sequence /*{{{*/
328 // ---------------------------------------------------------------------
329 /* This globs the operations and calls dpkg
331 * If it is called with "OutStatusFd" set to a valid file descriptor
332 * apt will report the install progress over this fd. It maps the
333 * dpkg states a package goes through to human readable (and i10n-able)
334 * names and calculates a percentage for each step.
336 bool pkgDPkgPM::Go(int OutStatusFd
)
338 unsigned int MaxArgs
= _config
->FindI("Dpkg::MaxArgs",8*1024);
339 unsigned int MaxArgBytes
= _config
->FindI("Dpkg::MaxArgBytes",32*1024);
341 if (RunScripts("DPkg::Pre-Invoke") == false)
344 if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false)
347 // prepare the progress reporting
350 // map the dpkg states to the operations that are performed
351 // (this is sorted in the same way as Item::Ops)
352 static const struct DpkgState DpkgStatesOpMap
[][5] = {
355 {"half-installed", N_("Preparing %s")},
356 {"unpacked", N_("Unpacking %s") },
359 // Configure operation
361 {"unpacked",N_("Preparing to configure %s") },
362 {"half-configured", N_("Configuring %s") },
363 { "installed", N_("Installed %s")},
368 {"half-configured", N_("Preparing for removal of %s")},
369 {"half-installed", N_("Removing %s")},
370 {"config-files", N_("Removed %s")},
375 {"config-files", N_("Preparing to completely remove %s")},
376 {"not-installed", N_("Completely removed %s")},
381 // the dpkg states that the pkg will run through, the string is
382 // the package, the vector contains the dpkg states that the package
384 map
<string
,vector
<struct DpkgState
> > PackageOps
;
385 // the dpkg states that are already done; the string is the package
386 // the int is the state that is already done (e.g. a package that is
387 // going to be install is already in state "half-installed")
388 map
<string
,int> PackageOpsDone
;
390 // init the PackageOps map, go over the list of packages that
391 // that will be [installed|configured|removed|purged] and add
392 // them to the PackageOps map (the dpkg states it goes through)
393 // and the PackageOpsTranslations (human readable strings)
394 for (vector
<Item
>::iterator I
= List
.begin(); I
!= List
.end();I
++)
396 string name
= (*I
).Pkg
.Name();
397 PackageOpsDone
[name
] = 0;
398 for(int i
=0; (DpkgStatesOpMap
[(*I
).Op
][i
]).state
!= NULL
; i
++)
400 PackageOps
[name
].push_back(DpkgStatesOpMap
[(*I
).Op
][i
]);
405 // this loop is runs once per operation
406 for (vector
<Item
>::iterator I
= List
.begin(); I
!= List
.end();)
408 vector
<Item
>::iterator J
= I
;
409 for (; J
!= List
.end() && J
->Op
== I
->Op
; J
++);
411 // Generate the argument list
412 const char *Args
[MaxArgs
+ 50];
413 if (J
- I
> (signed)MaxArgs
)
417 unsigned long Size
= 0;
418 string Tmp
= _config
->Find("Dir::Bin::dpkg","dpkg");
419 Args
[n
++] = Tmp
.c_str();
420 Size
+= strlen(Args
[n
-1]);
422 // Stick in any custom dpkg options
423 Configuration::Item
const *Opts
= _config
->Tree("DPkg::Options");
427 for (; Opts
!= 0; Opts
= Opts
->Next
)
429 if (Opts
->Value
.empty() == true)
431 Args
[n
++] = Opts
->Value
.c_str();
432 Size
+= Opts
->Value
.length();
436 char status_fd_buf
[20];
440 Args
[n
++] = "--status-fd";
441 Size
+= strlen(Args
[n
-1]);
442 snprintf(status_fd_buf
,sizeof(status_fd_buf
),"%i", fd
[1]);
443 Args
[n
++] = status_fd_buf
;
444 Size
+= strlen(Args
[n
-1]);
449 Args
[n
++] = "--force-depends";
450 Size
+= strlen(Args
[n
-1]);
451 Args
[n
++] = "--force-remove-essential";
452 Size
+= strlen(Args
[n
-1]);
453 Args
[n
++] = "--remove";
454 Size
+= strlen(Args
[n
-1]);
458 Args
[n
++] = "--force-depends";
459 Size
+= strlen(Args
[n
-1]);
460 Args
[n
++] = "--force-remove-essential";
461 Size
+= strlen(Args
[n
-1]);
462 Args
[n
++] = "--purge";
463 Size
+= strlen(Args
[n
-1]);
466 case Item::Configure
:
467 Args
[n
++] = "--configure";
468 Size
+= strlen(Args
[n
-1]);
472 Args
[n
++] = "--unpack";
473 Size
+= strlen(Args
[n
-1]);
474 Args
[n
++] = "--auto-deconfigure";
475 Size
+= strlen(Args
[n
-1]);
479 // Write in the file or package names
480 if (I
->Op
== Item::Install
)
482 for (;I
!= J
&& Size
< MaxArgBytes
; I
++)
484 if (I
->File
[0] != '/')
485 return _error
->Error("Internal Error, Pathname to install is not absolute '%s'",I
->File
.c_str());
486 Args
[n
++] = I
->File
.c_str();
487 Size
+= strlen(Args
[n
-1]);
492 for (;I
!= J
&& Size
< MaxArgBytes
; I
++)
494 Args
[n
++] = I
->Pkg
.Name();
495 Size
+= strlen(Args
[n
-1]);
501 if (_config
->FindB("Debug::pkgDPkgPM",false) == true)
503 for (unsigned int k
= 0; k
!= n
; k
++)
504 clog
<< Args
[k
] << ' ';
513 /* Mask off sig int/quit. We do this because dpkg also does when
514 it forks scripts. What happens is that when you hit ctrl-c it sends
515 it to all processes in the group. Since dpkg ignores the signal
516 it doesn't die but we do! So we must also ignore it */
517 sighandler_t old_SIGQUIT
= signal(SIGQUIT
,SIG_IGN
);
518 sighandler_t old_SIGINT
= signal(SIGINT
,SIG_IGN
);
522 _config
->Set("APT::Keep-Fds::",fd
[1]);
528 close(fd
[0]); // close the read end of the pipe
530 if (chdir(_config
->FindDir("DPkg::Run-Directory","/").c_str()) != 0)
533 if (_config
->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO
))
536 if ((Flags
= fcntl(STDIN_FILENO
,F_GETFL
,dummy
)) < 0)
539 // Discard everything in stdin before forking dpkg
540 if (fcntl(STDIN_FILENO
,F_SETFL
,Flags
| O_NONBLOCK
) < 0)
543 while (read(STDIN_FILENO
,&dummy
,1) == 1);
545 if (fcntl(STDIN_FILENO
,F_SETFL
,Flags
& (~(long)O_NONBLOCK
)) < 0)
549 /* No Job Control Stop Env is a magic dpkg var that prevents it
550 from using sigstop */
551 putenv("DPKG_NO_TSTP=yes");
552 execvp(Args
[0],(char **)Args
);
553 cerr
<< "Could not exec dpkg!" << endl
;
557 // clear the Keep-Fd again
558 _config
->Clear("APT::Keep-Fds",fd
[1]);
563 // we read from dpkg here
565 fcntl(_dpkgin
, F_SETFL
, O_NONBLOCK
);
566 close(fd
[1]); // close the write end of the pipe
568 // the read buffers for the communication with dpkg
569 char line
[1024] = {0,};
572 // the result of the waitpid call
575 while ((res
=waitpid(Child
,&Status
, WNOHANG
)) != Child
) {
577 // FIXME: move this to a function or something, looks ugly here
578 // error handling, waitpid returned -1
581 RunScripts("DPkg::Post-Invoke");
583 // Restore sig int/quit
584 signal(SIGQUIT
,old_SIGQUIT
);
585 signal(SIGINT
,old_SIGINT
);
586 return _error
->Errno("waitpid","Couldn't wait for subprocess");
589 // read a single char, make sure that the read can't block
590 // (otherwise we may leave zombies)
591 int len
= read(_dpkgin
, buf
, 1);
593 // nothing to read, wait a bit for more
600 // sanity check (should never happen)
601 if(strlen(line
) >= sizeof(line
)-10)
603 _error
->Error("got a overlong line from dpkg: '%s'",line
);
606 // append to line, check if we got a complete line
611 if (_config
->FindB("Debug::pkgDPkgProgressReporting",false) == true)
612 std::clog
<< "got from dpkg '" << line
<< "'" << std::endl
;
614 // the status we output
615 ostringstream status
;
617 /* dpkg sends strings like this:
618 'status: <pkg>: <pkg qstate>'
619 errors look like this:
620 '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
621 and conffile-prompt like this
622 'status: conffile-prompt: conffile : 'current-conffile' 'new-conffile' useredited distedited
626 // dpkg sends multiline error messages sometimes (see
627 // #374195 for a example. we should support this by
628 // either patching dpkg to not send multiline over the
629 // statusfd or by rewriting the code here to deal with
630 // it. for now we just ignore it and not crash
631 TokSplitString(':', line
, list
, sizeof(list
)/sizeof(list
[0]));
633 char *action
= _strstrip(list
[2]);
634 if( pkg
== NULL
|| action
== NULL
)
636 if (_config
->FindB("Debug::pkgDPkgProgressReporting",false) == true)
637 std::clog
<< "ignoring line: not enough ':'" << std::endl
;
638 // reset the line buffer
643 if(strncmp(action
,"error",strlen("error")) == 0)
645 status
<< "pmerror:" << list
[1]
646 << ":" << (Done
/float(Total
)*100.0)
650 write(OutStatusFd
, status
.str().c_str(), status
.str().size());
652 if (_config
->FindB("Debug::pkgDPkgProgressReporting",false) == true)
653 std::clog
<< "send: '" << status
.str() << "'" << endl
;
656 if(strncmp(action
,"conffile",strlen("conffile")) == 0)
658 status
<< "pmconffile:" << list
[1]
659 << ":" << (Done
/float(Total
)*100.0)
663 write(OutStatusFd
, status
.str().c_str(), status
.str().size());
665 if (_config
->FindB("Debug::pkgDPkgProgressReporting",false) == true)
666 std::clog
<< "send: '" << status
.str() << "'" << endl
;
670 vector
<struct DpkgState
> &states
= PackageOps
[pkg
];
671 const char *next_action
= NULL
;
672 if(PackageOpsDone
[pkg
] < states
.size())
673 next_action
= states
[PackageOpsDone
[pkg
]].state
;
674 // check if the package moved to the next dpkg state
675 if(next_action
&& (strcmp(action
, next_action
) == 0))
677 // only read the translation if there is actually a next
679 const char *translation
= _(states
[PackageOpsDone
[pkg
]].str
);
681 snprintf(s
, sizeof(s
), translation
, pkg
);
683 // we moved from one dpkg state to a new one, report that
684 PackageOpsDone
[pkg
]++;
686 // build the status str
687 status
<< "pmstatus:" << pkg
688 << ":" << (Done
/float(Total
)*100.0)
692 write(OutStatusFd
, status
.str().c_str(), status
.str().size());
693 if (_config
->FindB("Debug::pkgDPkgProgressReporting",false) == true)
694 std::clog
<< "send: '" << status
.str() << "'" << endl
;
697 if (_config
->FindB("Debug::pkgDPkgProgressReporting",false) == true)
698 std::clog
<< "(parsed from dpkg) pkg: " << pkg
699 << " action: " << action
<< endl
;
701 // reset the line buffer
706 // Restore sig int/quit
707 signal(SIGQUIT
,old_SIGQUIT
);
708 signal(SIGINT
,old_SIGINT
);
710 // Check for an error code.
711 if (WIFEXITED(Status
) == 0 || WEXITSTATUS(Status
) != 0)
713 // if it was set to "keep-dpkg-runing" then we won't return
714 // here but keep the loop going and just report it as a error
716 bool stopOnError
= _config
->FindB("Dpkg::StopOnError",true);
719 RunScripts("DPkg::Post-Invoke");
721 if (WIFSIGNALED(Status
) != 0 && WTERMSIG(Status
) == SIGSEGV
)
722 _error
->Error("Sub-process %s received a segmentation fault.",Args
[0]);
723 else if (WIFEXITED(Status
) != 0)
724 _error
->Error("Sub-process %s returned an error code (%u)",Args
[0],WEXITSTATUS(Status
));
726 _error
->Error("Sub-process %s exited unexpectedly",Args
[0]);
733 if (RunScripts("DPkg::Post-Invoke") == false)
738 // pkgDpkgPM::Reset - Dump the contents of the command list /*{{{*/
739 // ---------------------------------------------------------------------
741 void pkgDPkgPM::Reset()
743 List
.erase(List
.begin(),List
.end());