|
|
|
// -*- mode: cpp; mode: fold -*-
|
|
|
|
// Description /*{{{*/
|
|
|
|
// $Id: dpkgpm.cc,v 1.28 2004/01/27 02:25:01 mdz Exp $
|
|
|
|
/* ######################################################################
|
|
|
|
|
|
|
|
DPKG Package Manager - Provide an interface to dpkg
|
|
|
|
|
|
|
|
##################################################################### */
|
|
|
|
/*}}}*/
|
|
|
|
// Includes /*{{{*/
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include <apt-pkg/cachefile.h>
|
|
|
|
#include <apt-pkg/configuration.h>
|
|
|
|
#include <apt-pkg/depcache.h>
|
|
|
|
#include <apt-pkg/dpkgpm.h>
|
|
|
|
#include <apt-pkg/error.h>
|
|
|
|
#include <apt-pkg/fileutl.h>
|
|
|
|
#include <apt-pkg/install-progress.h>
|
|
|
|
#include <apt-pkg/packagemanager.h>
|
|
|
|
#include <apt-pkg/pkgrecords.h>
|
|
|
|
#include <apt-pkg/strutl.h>
|
|
|
|
#include <apt-pkg/cacheiterators.h>
|
|
|
|
#include <apt-pkg/macros.h>
|
|
|
|
#include <apt-pkg/pkgcache.h>
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <grp.h>
|
|
|
|
#include <pty.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <sys/select.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <termios.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cstring>
|
|
|
|
#include <iostream>
|
|
|
|
#include <map>
|
|
|
|
#include <set>
|
|
|
|
#include <string>
|
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include <apti18n.h>
|
|
|
|
/*}}}*/
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
APT_PURE static unsigned int
|
|
|
|
EnvironmentSize()
|
|
|
|
{
|
|
|
|
unsigned int size = 0;
|
|
|
|
char **envp = environ;
|
|
|
|
|
|
|
|
while (*envp != NULL)
|
|
|
|
size += strlen (*envp++) + 1;
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
class pkgDPkgPMPrivate
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
pkgDPkgPMPrivate() : stdin_is_dev_null(false), dpkgbuf_pos(0),
|
|
|
|
term_out(NULL), history_out(NULL),
|
fix PTY interaction on linux and kfreebsd
We run dpkg on its own pty, so we can log its output and have our own
output around it (like the progress bar), while also allowing debconf
and configfile prompts to happen.
In commit 223ae57d468fdcac451209a095047a07a5698212 we changed to
constantly reopening the slave for kfreebsd. This has the sideeffect
though that in some cases slave and master will lose their connection on
linux, so that no output is passed along anymore. We fix this by having
always an fd referencing the slave open (linux), but we don't use it
(kfreebsd).
Failing to get our PTY up and running has many (bad) consequences
including (not limited to, nor all at ones or in any case) garbled ouput,
no output, no logging, a (partial) mixture of the previous items, …
This commit is therefore also reshuffling quiet a bit of the creation
code to get especially the output part up and running on linux and the
logging for kfreebsd.
Note that the testcase tries to cover some cases, but this is an
interactivity issue so only interactive usage can really be a good test.
Closes: 765687
8 years ago
|
|
|
progress(NULL), tt_is_valid(false), master(-1),
|
|
|
|
slave(NULL), protect_slave_from_dying(-1),
|
|
|
|
direct_stdin(false)
|
|
|
|
{
|
|
|
|
dpkgbuf[0] = '\0';
|
|
|
|
}
|
|
|
|
~pkgDPkgPMPrivate()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
bool stdin_is_dev_null;
|
|
|
|
// the buffer we use for the dpkg status-fd reading
|
|
|
|
char dpkgbuf[1024];
|
|
|
|
int dpkgbuf_pos;
|
|
|
|
FILE *term_out;
|
|
|
|
FILE *history_out;
|
|
|
|
string dpkg_error;
|
|
|
|
APT::Progress::PackageManager *progress;
|
|
|
|
|
|
|
|
// pty stuff
|
|
|
|
struct termios tt;
|
fix PTY interaction on linux and kfreebsd
We run dpkg on its own pty, so we can log its output and have our own
output around it (like the progress bar), while also allowing debconf
and configfile prompts to happen.
In commit 223ae57d468fdcac451209a095047a07a5698212 we changed to
constantly reopening the slave for kfreebsd. This has the sideeffect
though that in some cases slave and master will lose their connection on
linux, so that no output is passed along anymore. We fix this by having
always an fd referencing the slave open (linux), but we don't use it
(kfreebsd).
Failing to get our PTY up and running has many (bad) consequences
including (not limited to, nor all at ones or in any case) garbled ouput,
no output, no logging, a (partial) mixture of the previous items, …
This commit is therefore also reshuffling quiet a bit of the creation
code to get especially the output part up and running on linux and the
logging for kfreebsd.
Note that the testcase tries to cover some cases, but this is an
interactivity issue so only interactive usage can really be a good test.
Closes: 765687
8 years ago
|
|
|
bool tt_is_valid;
|
|
|
|
int master;
|
|
|
|
char * slave;
|
fix PTY interaction on linux and kfreebsd
We run dpkg on its own pty, so we can log its output and have our own
output around it (like the progress bar), while also allowing debconf
and configfile prompts to happen.
In commit 223ae57d468fdcac451209a095047a07a5698212 we changed to
constantly reopening the slave for kfreebsd. This has the sideeffect
though that in some cases slave and master will lose their connection on
linux, so that no output is passed along anymore. We fix this by having
always an fd referencing the slave open (linux), but we don't use it
(kfreebsd).
Failing to get our PTY up and running has many (bad) consequences
including (not limited to, nor all at ones or in any case) garbled ouput,
no output, no logging, a (partial) mixture of the previous items, …
This commit is therefore also reshuffling quiet a bit of the creation
code to get especially the output part up and running on linux and the
logging for kfreebsd.
Note that the testcase tries to cover some cases, but this is an
interactivity issue so only interactive usage can really be a good test.
Closes: 765687
8 years ago
|
|
|
int protect_slave_from_dying;
|
|
|
|
|
|
|
|
// signals
|
|
|
|
sigset_t sigmask;
|
|
|
|
sigset_t original_sigmask;
|
|
|
|
|
|
|
|
bool direct_stdin;
|
|
|
|
};
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
// Maps the dpkg "processing" info to human readable names. Entry 0
|
|
|
|
// of each array is the key, entry 1 is the value.
|
|
|
|
const std::pair<const char *, const char *> PackageProcessingOps[] = {
|
|
|
|
std::make_pair("install", N_("Installing %s")),
|
|
|
|
std::make_pair("configure", N_("Configuring %s")),
|
|
|
|
std::make_pair("remove", N_("Removing %s")),
|
|
|
|
std::make_pair("purge", N_("Completely removing %s")),
|
|
|
|
std::make_pair("disappear", N_("Noting disappearance of %s")),
|
|
|
|
std::make_pair("trigproc", N_("Running post-installation trigger %s"))
|
|
|
|
};
|
|
|
|
|
|
|
|
const std::pair<const char *, const char *> * const PackageProcessingOpsBegin = PackageProcessingOps;
|
|
|
|
const std::pair<const char *, const char *> * const PackageProcessingOpsEnd = PackageProcessingOps + sizeof(PackageProcessingOps) / sizeof(PackageProcessingOps[0]);
|
|
|
|
|
|
|
|
// Predicate to test whether an entry in the PackageProcessingOps
|
|
|
|
// array matches a string.
|
|
|
|
class MatchProcessingOp
|
|
|
|
{
|
|
|
|
const char *target;
|
|
|
|
|
|
|
|
public:
|
|
|
|
MatchProcessingOp(const char *the_target)
|
|
|
|
: target(the_target)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(const std::pair<const char *, const char *> &pair) const
|
|
|
|
{
|
|
|
|
return strcmp(pair.first, target) == 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/* helper function to ionice the given PID
|
|
|
|
|
|
|
|
there is no C header for ionice yet - just the syscall interface
|
|
|
|
so we use the binary from util-linux
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
ionice(int PID)
|
|
|
|
{
|
|
|
|
if (!FileExists("/usr/bin/ionice"))
|
|
|
|
return false;
|
|
|
|
pid_t Process = ExecFork();
|
|
|
|
if (Process == 0)
|
|
|
|
{
|
|
|
|
char buf[32];
|
|
|
|
snprintf(buf, sizeof(buf), "-p%d", PID);
|
|
|
|
const char *Args[4];
|
|
|
|
Args[0] = "/usr/bin/ionice";
|
|
|
|
Args[1] = "-c3";
|
|
|
|
Args[2] = buf;
|
|
|
|
Args[3] = 0;
|
|
|
|
execv(Args[0], (char **)Args);
|
|
|
|
}
|
|
|
|
return ExecWait(Process, "ionice");
|
|
|
|
}
|
|
|
|
|
|
|
|
static std::string getDpkgExecutable()
|
|
|
|
{
|
|
|
|
string Tmp = _config->Find("Dir::Bin::dpkg","dpkg");
|
|
|
|
string const dpkgChrootDir = _config->FindDir("DPkg::Chroot-Directory", "/");
|
|
|
|
size_t dpkgChrootLen = dpkgChrootDir.length();
|
|
|
|
if (dpkgChrootDir != "/" && Tmp.find(dpkgChrootDir) == 0)
|
|
|
|
{
|
|
|
|
if (dpkgChrootDir[dpkgChrootLen - 1] == '/')
|
|
|
|
--dpkgChrootLen;
|
|
|
|
Tmp = Tmp.substr(dpkgChrootLen);
|
|
|
|
}
|
|
|
|
return Tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
// dpkgChrootDirectory - chrooting for dpkg if needed /*{{{*/
|
|
|
|
static void dpkgChrootDirectory()
|
|
|
|
{
|
|
|
|
std::string const chrootDir = _config->FindDir("DPkg::Chroot-Directory");
|
|
|
|
if (chrootDir == "/")
|
|
|
|
return;
|
|
|
|
std::cerr << "Chrooting into " << chrootDir << std::endl;
|
|
|
|
if (chroot(chrootDir.c_str()) != 0)
|
|
|
|
_exit(100);
|
|
|
|
if (chdir("/") != 0)
|
|
|
|
_exit(100);
|
|
|
|
}
|
|
|
|
/*}}}*/
|
|
|
|
|
|
|
|
|
|
|
|
// FindNowVersion - Helper to find a Version in "now" state /*{{{*/
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
/* This is helpful when a package is no longer installed but has residual
|
|
|
|
* config files
|
|
|
|
*/
|
|
|
|
static
|
|
|
|
pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg)
|
|
|
|
{
|
|
|
|
pkgCache::VerIterator Ver;
|
|
|
|
for (Ver = Pkg.VersionList(); Ver.end() == false; ++Ver)
|
|
|
|
for (pkgCache::VerFileIterator Vf = Ver.FileList(); Vf.end() == false; ++Vf)
|
|
|
|
for (pkgCache::PkgFileIterator F = Vf.File(); F.end() == false; ++F)
|
|
|
|
if (F->Archive != 0 && strcmp(F.Archive(), "now") == 0)
|
|
|
|
return Ver;
|
|
|
|
return Ver;
|
|
|
|
}
|
|
|
|
/*}}}*/
|
|
|
|
|
|
|
|
// DPkgPM::pkgDPkgPM - Constructor /*{{{*/
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
/* */
|
|
|
|
pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache)
|
|
|
|
: pkgPackageManager(Cache), pkgFailures(0), PackagesDone(0), PackagesTotal(0)
|
|
|
|
{
|
|
|
|
d = new pkgDPkgPMPrivate();
|
|
|
|
}
|
|
|
|
/*}}}*/
|
|
|
|
// DPkgPM::pkgDPkgPM - Destructor /*{{{*/
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
/* */
|
|
|
|
pkgDPkgPM::~pkgDPkgPM()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
/*}}}*/
|
|
|
|
// DPkgPM::Install - Install a package /*{{{*/
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
/* Add an install operation to the sequence list */
|
|
|
|
bool pkgDPkgPM::Install(PkgIterator Pkg,string File)
|
|
|
|
{
|
|
|
|
if (File.empty() == true || Pkg.end() == true)
|
|
|
|
return _error->Error("Internal Error, No file name for %s",Pkg.FullName().c_str());
|
|
|
|
|
|
|
|
// If the filename string begins with DPkg::Chroot-Directory, return the
|
|
|
|
// substr that is within the chroot so dpkg can access it.
|
|
|
|
string const chrootdir = _config->FindDir("DPkg::Chroot-Directory","/");
|
|
|
|
if (chrootdir != "/" && File.find(chrootdir) == 0)
|
|
|
|
{
|
|
|
|
size_t len = chrootdir.length();
|
|
|
|
if (chrootdir.at(len - 1) == '/')
|
|
|
|
len--;
|
|
|
|
List.push_back(Item(Item::Install,Pkg,File.substr(len)));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
List.push_back(Item(Item::Install,Pkg,File));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/*}}}*/
|
|
|
|
// DPkgPM::Configure - Configure a package /*{{{*/
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
/* Add a configure operation to the sequence list */
|
|
|
|
bool pkgDPkgPM::Configure(PkgIterator Pkg)
|
|
|
|
{
|
|
|
|
if (Pkg.end() == true)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
List.push_back(Item(Item::Configure, Pkg));
|
|
|
|
|
|
|
|
// Use triggers for config calls if we configure "smart"
|
|
|
|
// as otherwise Pre-Depends will not be satisfied, see #526774
|
|
|
|
if (_config->FindB("DPkg::TriggersPending", false) == true)
|
|
|
|
List.push_back(Item(Item::TriggersPending, PkgIterator()));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/*}}}*/
|
|
|
|
// DPkgPM::Remove - Remove a package /*{{{*/
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
/* Add a remove operation to the sequence list */
|
|
|
|
bool pkgDPkgPM::Remove(PkgIterator Pkg,bool Purge)
|
|
|
|
{
|
|
|
|
if (Pkg.end() == true)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (Purge == true)
|
|
|
|
List.push_back(Item(Item::Purge,Pkg));
|
|
|
|
else
|
|
|
|
List.push_back(Item(Item::Remove,Pkg));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/*}}}*/
|
|
|
|
// DPkgPM::SendPkgInfo - Send info for install-pkgs hook /*{{{*/
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
/* This is part of the helper script communication interface, it sends
|
|
|
|
very complete information down to the other end of the pipe.*/
|
|
|
|
bool pkgDPkgPM::SendV2Pkgs(FILE *F)
|
|
|
|
{
|
|
|
|
return SendPkgsInfo(F, 2);
|
|
|
|
}
|
|
|
|
bool pkgDPkgPM::SendPkgsInfo(FILE * const F, unsigned int const &Version)
|
|
|
|
{
|
|
|
|
// This version of APT supports only v3, so don't sent higher versions
|
|
|
|
if (Version <= 3)
|
|
|
|
fprintf(F,"VERSION %u\n", Version);
|
|
|
|
else
|
|
|
|
fprintf(F,"VERSION 3\n");
|
|
|
|
|
|
|
|
/* Write out all of the configuration directives by walking the
|
|
|
|
configuration tree */
|
|
|
|
const Configuration::Item *Top = _config->Tree(0);
|
|
|
|
for (; Top != 0;)
|
|
|
|
{
|
|
|
|
if (Top->Value.empty() == false)
|
|
|
|
{
|
|
|
|
fprintf(F,"%s=%s\n",
|
|
|
|
QuoteString(Top->FullTag(),"=\"\n").c_str(),
|
|
|
|
QuoteString(Top->Value,"\n").c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Top->Child != 0)
|
|
|
|
{
|
|
|
|
Top = Top->Child;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (Top != 0 && Top->Next == 0)
|
|
|
|
Top = Top->Parent;
|
|
|
|
if (Top != 0)
|
|
|
|
Top = Top->Next;
|
|
|
|
}
|
|
|
|
fprintf(F,"\n");
|
|
|
|
|
|
|
|
// Write out the package actions in order.
|
|
|
|
for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I)
|
|
|
|
{
|
|
|
|
if(I->Pkg.end() == true)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
pkgDepCache::StateCache &S = Cache[I->Pkg];
|
|
|
|
|
|
|
|
fprintf(F,"%s ",I->Pkg.Name());
|
|
|
|
|
|
|
|
// Current version which we are going to replace
|
|
|
|
pkgCache::VerIterator CurVer = I->Pkg.CurrentVer();
|
|
|
|
if (CurVer.end() == true && (I->Op == Item::Remove || I->Op == Item::Purge))
|
|
|
|
CurVer = FindNowVersion(I->Pkg);
|
|
|
|
|
|
|
|
if (CurVer.end() == true)
|
|
|
|
{
|
|
|
|
if (Version <= 2)
|
|
|
|
fprintf(F, "- ");
|
|
|
|
else
|
|
|
|
fprintf(F, "- - none ");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(F, "%s ", CurVer.VerStr());
|
|
|
|
if (Version >= 3)
|
|
|
|
fprintf(F, "%s %s ", CurVer.Arch(), CurVer.MultiArchType());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show the compare operator between current and install version
|
|
|
|
if (S.InstallVer != 0)
|
|
|
|
{
|
|
|
|
pkgCache::VerIterator const InstVer = S.InstVerIter(Cache);
|
|
|
|
int Comp = 2;
|
|
|
|
if (CurVer.end() == false)
|
|
|
|
Comp = InstVer.CompareVer(CurVer);
|
|
|
|
if (Comp < 0)
|
|
|
|
fprintf(F,"> ");
|
|
|
|
else if (Comp == 0)
|
|
|
|
fprintf(F,"= ");
|
|
|
|
else if (Comp > 0)
|
|
|
|
fprintf(F,"< ");
|
|
|
|
fprintf(F, "%s ", InstVer.VerStr());
|
|
|
|
if (Version >= 3)
|
|
|
|
fprintf(F, "%s %s ", InstVer.Arch(), InstVer.MultiArchType());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Version <= 2)
|
|
|
|
fprintf(F, "> - ");
|
|
|
|
else
|
|
|
|
fprintf(F, "> - - none ");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show the filename/operation
|
|
|
|
if (I->Op == Item::Install)
|
|
|
|
{
|
|
|
|
// No errors here..
|
|
|
|
if (I->File[0] != '/')
|
|
|
|
fprintf(F,"**ERROR**\n");
|
|
|
|
else
|
|
|
|
fprintf(F,"%s\n",I->File.c_str());
|
|
|
|
}
|
|
|
|
else if (I->Op == Item::Configure)
|
|
|
|
fprintf(F,"**CONFIGURE**\n");
|
|
|
|
else if (I->Op == Item::Remove ||
|
|
|
|
I->Op == Item::Purge)
|
|
|
|
fprintf(F,"**REMOVE**\n");
|
|
|
|
|
|
|
|
if (ferror(F) != 0)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/*}}}*/
|
|
|
|
// DPkgPM::RunScriptsWithPkgs - Run scripts with package names on stdin /*{{{*/
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
/* This looks for a list of scripts to run from the configuration file
|
|
|
|
each one is run and is fed on standard input a list of all .deb files
|
|
|
|
that are due to be installed. */
|
|
|
|
bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
|
|
|
|
{
|
|
|
|
bool result = true;
|
|
|
|
|
|
|
|
Configuration::Item const *Opts = _config->Tree(Cnf);
|
|
|
|
if (Opts == 0 || Opts->Child == 0)
|
|
|
|
return true;
|
|
|
|
Opts = Opts->Child;
|
|
|
|
|
|
|
|
sighandler_t old_sigpipe = signal(SIGPIPE, SIG_IGN);
|
|
|
|
|
|
|
|
unsigned int Count = 1;
|
|
|
|
for (; Opts != 0; Opts = Opts->Next, Count++)
|
|
|
|
{
|
|
|
|
if (Opts->Value.empty() == true)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if(_config->FindB("Debug::RunScripts", false) == true)
|
|
|
|
std::clog << "Running external script with list of all .deb file: '"
|
|
|
|
<< Opts->Value << "'" << std::endl;
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
unsigned int Version = _config->FindI(OptSec+"::Version",1);
|
|
|
|
unsigned int InfoFD = _config->FindI(OptSec + "::InfoFD", STDIN_FILENO);
|
|
|
|
|
|
|
|
// Create the pipes
|
|
|
|
std::set<int> KeepFDs;
|
|
|
|
MergeKeepFdsFromConfiguration(KeepFDs);
|
|
|
|
int Pipes[2];
|
|
|
|
if (pipe(Pipes) != 0) {
|
|
|
|
result = _error->Errno("pipe","Failed to create IPC pipe to subprocess");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (InfoFD != (unsigned)Pipes[0])
|
|
|
|
SetCloseExec(Pipes[0],true);
|
|
|
|
else
|
|
|
|
KeepFDs.insert(Pipes[0]);
|
|
|
|
|
|
|
|
|
|
|
|
SetCloseExec(Pipes[1],true);
|
|
|
|
|
|
|
|
// Purified Fork for running the script
|
|
|
|
pid_t Process = ExecFork(KeepFDs);
|
|
|
|
if (Process == 0)
|
|
|
|
{
|
|
|
|
// Setup the FDs
|
|
|
|
dup2(Pipes[0], InfoFD);
|
|
|
|
SetCloseExec(STDOUT_FILENO,false);
|
|
|
|
SetCloseExec(STDIN_FILENO,false);
|
|
|
|
SetCloseExec(STDERR_FILENO,false);
|
|
|
|
|
|
|
|
string hookfd;
|
|
|
|
strprintf(hookfd, "%d", InfoFD);
|
|
|
|
setenv("APT_HOOK_INFO_FD", hookfd.c_str(), 1);
|
|
|
|
|
|
|
|
dpkgChrootDirectory();
|
|
|
|
const char *Args[4];
|
|
|
|
Args[0] = "/bin/sh";
|
|
|
|
Args[1] = "-c";
|
|
|
|
Args[2] = Opts->Value.c_str();
|
|
|
|
Args[3] = 0;
|
|
|
|
execv(Args[0],(char **)Args);
|
|
|
|
_exit(100);
|
|
|
|
}
|
|
|
|
close(Pipes[0]);
|
|
|
|
FILE *F = fdopen(Pipes[1],"w");
|
|
|
|
if (F == 0) {
|
|
|
|
result = _error->Errno("fdopen","Faild to open new FD");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Feed it the filenames.
|
|
|
|
if (Version <= 1)
|
|
|
|
{
|
|
|
|
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)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
SendPkgsInfo(F, Version);
|
|
|
|
|
|
|
|
fclose(F);
|
|
|
|
|
|
|
|
// Clean up the sub process
|
|
|
|
if (ExecWait(Process,Opts->Value.c_str()) == false) {
|
|
|
|
result = _error->Error("Failure running script %s",Opts->Value.c_str());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
signal(SIGPIPE, old_sigpipe);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
/*}}}*/
|
|
|
|
// DPkgPM::DoStdin - Read stdin and pass to master pty /*{{{*/
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
/*
|
|
|
|
*/
|
|
|
|
void pkgDPkgPM::DoStdin(int master)
|
|
|
|
{
|
|
|
|
unsigned char input_buf[256] = {0,};
|
|
|
|
ssize_t len = read(STDIN_FILENO, input_buf, sizeof(input_buf));
|
|
|
|
if (len)
|
|
|
|
FileFd::Write(master, input_buf, len);
|
|
|
|
else
|
|
|
|
d->stdin_is_dev_null = true;
|
|
|
|
}
|
|
|
|
/*}}}*/
|
|
|
|
// DPkgPM::DoTerminalPty - Read the terminal pty and write log /*{{{*/
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
/*
|
|
|
|
* read the terminal pty and write log
|
|
|
|
*/
|
|
|
|
void pkgDPkgPM::DoTerminalPty(int master)
|
|
|
|
{
|
|
|
|
unsigned char term_buf[1024] = {0,0, };
|
|
|
|
|
|
|
|
ssize_t len=read(master, term_buf, sizeof(term_buf));
|
|
|
|
if(len == -1 && errno == EIO)
|
|
|
|
{
|
|
|
|
// this happens when the child is about to exit, we
|
|
|
|
// give it time to actually exit, otherwise we run
|
|
|
|
// into a race so we sleep for half a second.
|
|
|
|
struct timespec sleepfor = { 0, 500000000 };
|
|
|
|
nanosleep(&sleepfor, NULL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(len <= 0)
|
|
|
|
return;
|
|
|
|
FileFd::Write(1, term_buf, len);
|
|
|
|
if(d->term_out)
|
|
|
|
fwrite(term_buf, len, sizeof(char), d->term_out);
|
|
|
|
}
|
|
|
|
/*}}}*/
|
|
|
|
// DPkgPM::ProcessDpkgStatusBuf /*{{{*/
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
/*
|
|
|
|
*/
|
|
|
|
void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
|
|
|
|
{
|
|
|
|
bool const Debug = _config->FindB("Debug::pkgDPkgProgressReporting",false);
|
|
|
|
if (Debug == true)
|
|
|
|
std::clog << "got from dpkg '" << line << "'" << std::endl;
|
|
|
|
|
|
|
|
/* dpkg sends strings like this:
|
|
|
|
'status: <pkg>: <pkg qstate>'
|
|
|
|
'status: <pkg>:<arch>: <pkg qstate>'
|
|
|
|
|
|
|
|
'processing: {install,upgrade,configure,remove,purge,disappear,trigproc}: pkg'
|
|
|
|
'processing: {install,upgrade,configure,remove,purge,disappear,trigproc}: trigger'
|
|
|
|
*/
|
|
|
|
|
|
|
|
// we need to split on ": " (note the appended space) as the ':' is
|
|
|
|
// part of the pkgname:arch information that dpkg sends
|
|
|
|
//
|
|
|
|
// A dpkg error message may contain additional ":" (like
|
|
|
|
// "failed in buffer_write(fd) (10, ret=-1): backend dpkg-deb ..."
|
|
|
|
// so we need to ensure to not split too much
|
|
|
|
std::vector<std::string> list = StringSplit(line, ": ", 4);
|
|
|
|
if(list.size() < 3)
|
|
|
|
{
|
|
|
|
if (Debug == true)
|
|
|
|
std::clog << "ignoring line: not enough ':'" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// build the (prefix, pkgname, action) tuple, position of this
|
|
|
|
// is different for "processing" or "status" messages
|
|
|
|
std::string prefix = APT::String::Strip(list[0]);
|
|
|
|
std::string pkgname;
|
|
|
|
std::string action;
|
|
|
|
|
|
|
|
// "processing" has the form "processing: action: pkg or trigger"
|
|
|
|
// with action = ["install", "upgrade", "configure", "remove", "purge",
|
|
|
|
// "disappear", "trigproc"]
|
|
|
|
if (prefix == "processing")
|
|
|
|
{
|
|
|
|
pkgname = APT::String::Strip(list[2]);
|
|
|
|
action = APT::String::Strip(list[1]);
|
|
|
|
// we don't care for the difference (as dpkg doesn't really either)
|
|
|
|
if (action == "upgrade")
|
|
|
|
action = "install";
|
|
|
|
}
|
|
|
|
// "status" has the form: "status: pkg: state"
|
|
|
|
// with state in ["half-installed", "unpacked", "half-configured",
|
|
|
|
// "installed", "config-files", "not-installed"]
|
|
|
|
else if (prefix == "status")
|
|
|
|
{
|
|
|
|
pkgname = APT::String::Strip(list[1]);
|
|
|
|
action = APT::String::Strip(list[2]);
|
|
|
|
} else {
|
|
|
|
if (Debug == true)
|
|
|
|
std::clog << "unknown prefix '" << prefix << "'" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* handle the special cases first:
|
|
|
|
|
|
|
|
errors look like this:
|
|
|
|
'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
|
|
|
|
and conffile-prompt like this
|
|
|
|
'status:/etc/compiz.conf/compiz.conf : conffile-prompt: 'current-conffile' 'new-conffile' useredited distedited
|
|
|
|
*/
|
|
|
|
if (prefix == "status")
|
|
|
|
{
|
|
|
|
if(action == "error")
|
|
|
|
{
|
|
|
|
d->progress->Error(pkgname, PackagesDone, PackagesTotal,
|
|
|
|
list[3]);
|
|
|
|
pkgFailures++;
|
|
|
|
WriteApportReport(pkgname.c_str(), list[3].c_str());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if(action == "conffile-prompt")
|
|
|
|
{
|
|
|
|
d->progress->ConffilePrompt(pkgname, PackagesDone, PackagesTotal,
|
|
|
|
list[3]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// at this point we know that we should have a valid pkgname, so build all
|
|
|
|
// the info from it
|
|
|
|
|
|
|
|
// dpkg does not always send "pkgname:arch" so we add it here if needed
|
|
|