You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
adept/adept/adept/dpkgpm.cpp

464 lines
13 KiB

/** -*- C++ -*-
@file adept/dpkgpm.cpp
@author Peter Rockai <me@mornfall.net>
*/
#include <cstddef>
#include <apt-pkg/configuration.h>
#include <apt-pkg/error.h>
#include "dpkgpm.h"
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <apt-front/cache/cache.h>
#include <apt-front/cache/component/packages.h>
#include <apt-front/cache/entity/package.h>
#include <iostream>
#include <sstream>
using namespace std;
using namespace aptFront;
using namespace cache;
/* === PkgSystem === */
DPkgPM::DPkgPM (pkgDepCache *C)
: pkgDPkgPM (C), m_seenOpCount( 0 ), m_totalOpCount( 0 )
{
}
bool DPkgPM::Go( int )
{
cerr << "DPkgPM::Go()" << endl;
computeTotals();
if (runScripts ("DPkg::Pre-Invoke", false) == false)
return false;
if (runScripts ("DPkg::Pre-Install-Pkgs", true) == false)
return false;
for (vector<Item>::iterator I = List.begin(); I != List.end();) {
char *const *argv;
if (! setupArgs (&argv, I))
return false;
cerr << "running '";
for (unsigned int k = 0; argv [k]; k++)
cerr << argv[k] << ' ';
cerr << "'" << endl;
if (_config->FindB("Debug::pkgDPkgPM",false) == true)
{
for (unsigned int k = 0; argv [k]; k++)
cerr << argv[k] << ' ';
cerr << endl;
continue;
}
if (! forkDpkg (argv))
return false;
delete[] argv;
if (! runScripts ("DPkg::Post-Invoke", false))
return false;
}
return true;
}
void DPkgPM::computeTotals()
{
m_totalOpCount = 0;
for (vector<Item>::iterator I = List.begin(); I != List.end();I++)
{
entity::Package p = cache::Global::get().packages().packageByName(
I->Pkg.Name() );
int x = 0;
switch ( I->Op ) {
case Item::Remove: x = 4; break;
case Item::Install: p.markedUpgrade() ? x = 3 : x = 2; break;
case Item::Purge: x = 2; break;
case Item::Configure: x = 3; break;
}
m_totalOpCount += x;
}
}
bool DPkgPM::forkDpkg (char *const argv[])
{
cerr << "DPkgPM::forkDpkg ()" << endl;
cout << flush;
clog << flush;
cerr << flush;
/* Mask off sig int/quit. We do this because dpkg also does when
it forks scripts. What happens is that when you hit ctrl-c it sends
it to all processes in the group. Since dpkg ignores the signal
it doesn't die but we do! So we must also ignore it */
sighandler_t old_SIGQUIT = signal(SIGQUIT,SIG_IGN);
sighandler_t old_SIGINT = signal(SIGINT,SIG_IGN);
// Fork dpkg
pid_t Child = ExecFork();
if (Child == 0) {
if (! setupChild ()) {
cerr << "Error in dpkg post-fork setup!" << endl;
_exit (100);
}
execvp (argv [0], argv);
cerr << "Error executing dpkg!" << endl;
_exit (100);
}
close( m_dpkgPipe[1] );
fcntl( m_dpkgPipe[0], F_SETFL, O_NONBLOCK );
int tqStatus = 0;
int ret = 0;
while ((ret = waitpid (Child, &tqStatus, WNOHANG)) != Child) {
if (errno == EINTR || ret == 0) {
dpkgMonitor ();
usleep (200000); // 0.2 second hang
continue;
}
runScripts ("DPkg::Post-Invoke", false);
signal(SIGQUIT,old_SIGQUIT);
signal(SIGINT,old_SIGINT);
return _error -> Errno ("waitpid","Couldn't wait for subprocess");
}
signal(SIGQUIT,old_SIGQUIT);
signal(SIGINT,old_SIGINT);
// Check for an error code.
if (WIFEXITED(tqStatus) == 0 || WEXITSTATUS(tqStatus) != 0)
{
runScripts ("DPkg::Post-Invoke", false);
if (WIFSIGNALED(tqStatus) != 0 && WTERMSIG(tqStatus) == SIGSEGV)
return _error->Error("Sub-process %s received a segmentation fault.", argv [0]);
if (WIFEXITED(tqStatus) != 0)
return _error->Error("Sub-process %s returned an error code (%u)", argv[0], WEXITSTATUS(tqStatus));
return _error->Error("Sub-process %s exited unexpectedly", argv[0]);
}
return true;
}
bool DPkgPM::setupArgs (char *const**a, std::vector<Item>::iterator &I)
{
cerr << "DPkgPM::setupArgs ()" << endl;
unsigned int MaxArgs = _config->FindI("Dpkg::MaxArgs",350);
unsigned int MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes",8192);
vector<Item>::iterator J = I;
for (; J != List.end() && J->Op == I->Op; J++);
// Generate the argument list
const char **Args = new const char *[MaxArgs + 50];
if (J - I > (signed)MaxArgs)
J = I + MaxArgs;
unsigned int n = 0;
unsigned long Size = 0;
string Tmp = _config->Find("Dir::Bin::dpkg","dpkg");
Args[n++] = Tmp.c_str();
Size += strlen(Args[n-1]);
// Stick in any custom dpkg options
Configuration::Item const *Opts = _config->Tree("DPkg::Options");
if (Opts != 0)
{
Opts = Opts->Child;
for (; Opts != 0; Opts = Opts->Next)
{
if (Opts->Value.empty() == true)
continue;
Args[n++] = Opts->Value.c_str();
Size += Opts->Value.length();
}
}
pipe( m_dpkgPipe );
stringstream fds;
fds << m_dpkgPipe[1];
std::cerr << "reading end of the pipe: " << m_dpkgPipe[0] << std::endl;
Args[n++] = "--status-fd";
Size += strlen(Args[n-1]);
// bah, we leak a string every time we run dpkg... silly eh
Args[n++] = ( new string( fds.str() ) )->c_str();
Size += strlen(Args[n-1]);
switch (I->Op)
{
case Item::Remove:
Args[n++] = "--force-depends";
Size += strlen(Args[n-1]);
Args[n++] = "--force-remove-essential";
Size += strlen(Args[n-1]);
Args[n++] = "--remove";
Size += strlen(Args[n-1]);
m_currentOp = ORemove;
break;
case Item::Purge:
Args[n++] = "--force-depends";
Size += strlen(Args[n-1]);
Args[n++] = "--force-remove-essential";
Size += strlen(Args[n-1]);
Args[n++] = "--purge";
Size += strlen(Args[n-1]);
m_currentOp = OPurge;
break;
case Item::Configure:
Args[n++] = "--configure";
Size += strlen(Args[n-1]);
m_currentOp = OConfigure;
break;
case Item::Install:
Args[n++] = "--unpack";
Size += strlen(Args[n-1]);
m_currentOp = OInstall;
break;
}
// Write in the file or package names
if (I->Op == Item::Install)
{
for (;I != J && Size < MaxArgBytes; I++)
{
if (I->File[0] != '/')
return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
Args[n++] = I->File.c_str();
Size += strlen(Args[n-1]);
}
}
else
{
for (;I != J && Size < MaxArgBytes; I++)
{
Args[n++] = I->Pkg.Name();
Size += strlen(Args[n-1]);
}
}
Args[n] = 0;
J = I;
*a = (char *const *)Args;
return true;
}
bool DPkgPM::setupChild ()
{
// cerr << "DPkgPM::setupChild ()" << endl;
if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0)
return false;
if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO))
{
int Flags,dummy;
if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0)
return false;
// Discard everything in stdin before forking dpkg
if (fcntl(STDIN_FILENO,F_SETFL,Flags | O_NONBLOCK) < 0)
return false;
while (read(STDIN_FILENO,&dummy,1) == 1);
if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0)
return false;
}
/* No Job Control Stop Env is a magic dpkg var that prevents it
from using sigstop */
putenv("DPKG_NO_TSTP=yes");
close( m_dpkgPipe[0] );
return true;
}
void DPkgPM::dpkgMonitor ()
{
char buf[1024];
int r = read( m_dpkgPipe[0], buf, 1023 );
if ( r > 0 ) {
buf[r] = 0;
std::string b( buf );
// parse status updates from dpkg
while ( true ) {
std::string::size_type colon, nl = b.find( '\n' );
m_statusBuffer.append( string( b, 0, nl ) );
if ( nl == std::string::npos )
break;
// cerr << "dpkg status completed line: " << m_statusBuffer << endl;
colon = m_statusBuffer.find( ": " );
string l( m_statusBuffer, 0, colon );
string r( m_statusBuffer, colon + 2, string::npos );
if ( l == "status" ) {
colon = r.find( ": " );
std::string p( r, 0, colon );
r = string( r, colon + 2, string::npos );
colon = r.find( ": " );
std::string e( r, 0, colon );
if ( colon == string::npos )
r = "";
else
r = string( r, colon + 2, string::npos );
updatetqStatus( p, e, r );
}
b = string( b, nl + 1, string::npos );
m_statusBuffer = string();
nl = b.find( '\n' );
}
}
}
void DPkgPM::updatetqStatus( std::string pkg, std::string ev, std::string r )
{
//std::cerr << "DPkgPM::updateStatus " << pkg << " " << ev << " " << r
// << std::endl;
if (ev.find("error") != string::npos) {
char* list[5];
char *line = strdup(m_statusBuffer.c_str());
TokSplitString(':', line, list, sizeof(list)/sizeof(list[0]));
WriteApportReport(list[1], list[3]);
free(line);
return;
}
OpAndtqStatus os = std::make_pair( m_currentOp, ev );
if ( m_seenOps[ std::make_pair( os, pkg ) ] == 0 ) {
m_seenOpCount++;
m_seenOps[ std::make_pair( os, pkg ) ] = 1;
}
}
bool DPkgPM::runScripts (const char *Cnf, bool sP)
{
cerr << "DPkgPM::runScripts ('" << Cnf << "', " << sP << ")" << endl;
Configuration::Item const *Opts = _config->Tree(Cnf);
if (Opts == 0 || Opts->Child == 0)
return true;
Opts = Opts->Child;
unsigned int Count = 1;
for (; Opts != 0; Opts = Opts->Next, Count++)
{
if (Opts->Value.empty() == true)
continue;
// Determine the protocol version
string OptSec = Opts->Value;
string::size_type Pos;
if ((Pos = OptSec.find(' ')) == string::npos || Pos == 0)
Pos = OptSec.length();
OptSec = "DPkg::Tools::Options::" + string(Opts->Value.c_str(),Pos);
m_version = _config->FindI (OptSec + "::Version", 1);
// Purified Fork for running the script
// pid_t Process = ExecFork();
forkScript (Opts->Value.c_str(), sP);
}
return true;
}
bool DPkgPM::SendV1Pkgs (FILE *F)
{
bool Die = false;
for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I)
{
// Only deal with packages to be installed from .deb
if (I->Op != Item::Install)
continue;
// No errors here..
if (I->File[0] != '/')
continue;
/* Feed the filename of each package that is pending install
into the pipe. */
fprintf(F,"%s\n",I->File.c_str());
if (ferror(F) != 0)
{
Die = true;
break;
}
}
return ! Die;
}
bool DPkgPM::forkScript (const char *cmd, bool fP)
{
cerr << "DPkgPM::forkScript ()" << endl;
if (fP) {
if (pipe( m_scriptPipe ) != 0)
return _error -> Errno("pipe", "Failed to create IPC pipe to subprocess");
SetCloseExec( m_scriptPipe[ 0 ], true );
SetCloseExec( m_scriptPipe[ 1 ], true );
}
pid_t Process = ExecFork ();
if (Process == 0)
{
setupScript (cmd, fP);
const char *Args[4];
Args[0] = "/bin/sh";
Args[1] = "-c";
Args[2] = cmd;
Args[3] = 0;
execv(Args[0],(char **)Args);
_exit(100);
}
if (fP) {
if (! feedPackages ())
return _error -> Error ("Failed feeding packages to script");
}
// Clean up the sub process
if (ExecWait (Process, cmd) == false)
return _error -> Error("Failure running script %s", cmd);
}
void DPkgPM::setupScript (const char * /*cmd*/, bool fP)
{
cerr << "DPkgPM::setupScript ()" << endl;
if (fP) {
dup2 (m_scriptPipe [0], STDIN_FILENO);
SetCloseExec(STDOUT_FILENO, false);
SetCloseExec(STDIN_FILENO, false);
SetCloseExec(STDERR_FILENO, false);
}
}
bool DPkgPM::feedPackages ()
{
close(m_scriptPipe[ 0 ]);
FILE *F = fdopen(m_scriptPipe[ 1 ], "w");
if (F == 0)
return _error->Errno("fdopen","Faild to open new FD");
// Feed it the filenames.
bool Die = false;
if (m_version <= 1)
Die = !SendV1Pkgs (F);
else
Die = !SendV2Pkgs(F);
fclose(F);
return ! Die;
}