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.
452 lines
12 KiB
452 lines
12 KiB
/** -*- C++ -*-
|
|
@file adept/dpkgpm.cpp
|
|
@author Peter Rockai <me@mornfall.net>
|
|
*/
|
|
|
|
#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.tqfind( '\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.tqfind( ": " );
|
|
string l( m_statusBuffer, 0, colon );
|
|
string r( m_statusBuffer, colon + 2, string::npos );
|
|
|
|
if ( l == "status" ) {
|
|
colon = r.tqfind( ": " );
|
|
std::string p( r, 0, colon );
|
|
r = string( r, colon + 2, string::npos );
|
|
|
|
colon = r.tqfind( ": " );
|
|
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.tqfind( '\n' );
|
|
}
|
|
}
|
|
}
|
|
|
|
void DPkgPM::updatetqStatus( std::string pkg, std::string ev, std::string r )
|
|
{
|
|
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.tqfind(' ')) == 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;
|
|
}
|