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.

2112 lines
67 KiB

// -*- mode: cpp; mode: fold -*-
// Description /*{{{*/
/* ######################################################################
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/debsystem.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/strutl.h>
#include <apt-pkg/statechanges.h>
#include <apt-pkg/cacheiterators.h>
#include <apt-pkg/macros.h>
#include <apt-pkg/pkgcache.h>
#include <apt-pkg/version.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.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 <sys/types.h>
#include <dirent.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include <algorithm>
#include <array>
#include <cstring>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <sstream>
#include <numeric>
* merged from main Patches applied: * apt@packages.debian.org/apt--main--0--patch-106 Restore lost changelog entries * apt@packages.debian.org/apt--main--0--patch-107 Merge michael.vogt@ubuntu.com--2005/apt--progress-reporting--0 * apt@packages.debian.org/apt--main--0--patch-108 Merge michael.vogt@ubuntu.com--2005/apt--progress-reporting--0 * apt@packages.debian.org/apt--main--0--patch-109 Merge michael.vogt@ubuntu.com--2005/apt--progress-reporting--0 * apt@packages.debian.org/apt--main--0--patch-110 Merge michael.vogt@ubuntu.com--2005/apt--progress-reporting--0 * michael.vogt@ubuntu.com--2005/apt--fixes--0--patch-5 * merged with apt--main--0 * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--base-0 tag of apt@packages.debian.org/apt--main--0--patch-85 * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-1 * inital proof of concept code, understands what dpkg tells it already * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-2 * progress reporting works now * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-3 * added "APT::Status-Fd" variable * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-4 * do i18n now too * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-5 * define N_(x) if it is not defined already * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-6 * PackageManager::DoInstall(int status_fd) added (does not break the ABI) * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-7 * merged with apt--fixes--0 to make it build again * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-8 * added support for "error" and "conffile-prompt" messages from dpkg * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-9 merge with main * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-10 * use sizeof() for all snprintf() uses; fix a potential line break problem in the status reading code; changed the N_() to _() calls * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-11 * added APT::KeepFDs configuration list for file descriptors that apt should leave open (needed for various frontends like debconf, synaptic) * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-12 * fixed a API breakage * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-13 * doc added, should be releasable now * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-14 * merged with apt--main--0 * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-15 * more source comments, added Debug::DpkgPM debug code to inspect the dpkg<->apt communication, broke the abi (ok with matt) * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-16 * the progress reporting has it's own "Debug::pkgDPkgProgressReporting" debug variable now * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-17 * merged PackageOps and TranslatedPackageOps into a single Map with the new DpkgState struct * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-18 * clear the APT::Keep-Fds configuration when it's no longer needed * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-19 * rewrote the reading from dpkg so that it never blocks * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-20 * merged the two status arrays into one * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-21 * added support for download progress reporting too (for Kamion and base-config) * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-22 * ABI break; added Configuration::Clear(string List, {int,string} value) added (to remove a single Value from a list); test/conf_clear.cc added * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-23 * remvoed a debug string * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-24 * soname changed, fixed a bug in the parsing code when dpkg send the same state more than once (at the end) * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-25 * merged with apt@packages.debian.org/apt--main--0, added changelog entry for the 0.6.40.1 upload * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-26 * fix a bug when out-of-order states are send from dpkg * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-27 * changelog update * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-28 * a real changelog entry now * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-29 * changelog finalized * michael.vogt@ubuntu.com--2005/apt--progress-reporting--0--patch-30 * propper (and sane) support for pmerror and pmconffile added
17 years ago
#include <apti18n.h>
/*}}}*/
using namespace std;
APT_PURE static string AptHistoryRequestingUser() /*{{{*/
{
const char* EnvKeys[]{"SUDO_UID", "PKEXEC_UID", "PACKAGEKIT_CALLER_UID"};
for (const auto &Key: EnvKeys)
{
if (getenv(Key) != nullptr)
{
int uid = atoi(getenv(Key));
if (uid > 0) {
struct passwd pwd;
struct passwd *result;
char buf[255];
if (getpwuid_r(uid, &pwd, buf, sizeof(buf), &result) == 0 && result != NULL) {
std::string res;
strprintf(res, "%s (%d)", pwd.pw_name, uid);
return res;
}
}
}
}
return "";
}
/*}}}*/
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),
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];
size_t dpkgbuf_pos;
FILE *term_out;
FILE *history_out;
string dpkg_error;
APT::Progress::PackageManager *progress;
// pty stuff
struct termios tt;
bool tt_is_valid;
int master;
char * slave;
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:
explicit 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;
}
};
}
// ionice - 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");
}
/*}}}*/
// 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;
}
/*}}}*/
static pkgCache::VerIterator FindToBeRemovedVersion(pkgCache::PkgIterator const &Pkg)/*{{{*/
{
auto const PV = Pkg.CurrentVer();
if (PV.end() == false)
return PV;
return FindNowVersion(Pkg);
}
/*}}}*/
// DPkgPM::pkgDPkgPM - Constructor /*{{{*/
// ---------------------------------------------------------------------
/* */
pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache)
: pkgPackageManager(Cache),d(new pkgDPkgPMPrivate()), pkgFailures(0), PackagesDone(0), PackagesTotal(0)
{
}
/*}}}*/
// 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;
static bool interrupted = false;
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);
sighandler_t old_sigint = signal(SIGINT, [](int signum){
interrupted = true;
});
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);
debSystem::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","Failed 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(SIGINT, old_sigint);
signal(SIGPIPE, old_sigpipe);
if (interrupted)
result = _error->Error("Interrupted");
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'