Logo Search packages:      
Sourcecode: adept version File versions  Download package

dpkgpm.cpp

/** -*- 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 Status = 0;
    int ret = 0;
    while ((ret = waitpid (Child, &Status, 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(Status) == 0 || WEXITSTATUS(Status) != 0)
    {
        runScripts ("DPkg::Post-Invoke", false);
        if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV)
            return _error->Error("Sub-process %s received a segmentation fault.", argv [0]);

        if (WIFEXITED(Status) != 0)
            return _error->Error("Sub-process %s returned an error code (%u)", argv[0], WEXITSTATUS(Status));

        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 );
                updateStatus( p, e, r );
            }

            b = string( b, nl + 1, string::npos );
            m_statusBuffer = string();
            nl = b.find( '\n' );
        }
    }
}

void DPkgPM::updateStatus( std::string pkg, std::string ev, std::string r )
{
    OpAndStatus 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;
}

Generated by  Doxygen 1.6.0   Back to index