/** -*- C++ -*- @file adept/dpkgpm.cpp @author Peter Rockai */ #include #include #include #include "dpkgpm.h" #include #include #include #include #include #include #include #include #include #include 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::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::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::iterator &I) { cerr << "DPkgPM::setupArgs ()" << endl; unsigned int MaxArgs = _config->FindI("Dpkg::MaxArgs",350); unsigned int MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes",8192); vector::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::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; }