573 lines
17 KiB
C++
573 lines
17 KiB
C++
// -*- mode: cpp; mode: fold -*-
|
|
// Description /*{{{*/
|
|
/* ######################################################################
|
|
|
|
System - Abstraction for running on different systems.
|
|
|
|
Basic general structure..
|
|
|
|
##################################################################### */
|
|
/*}}}*/
|
|
// Include Files /*{{{*/
|
|
#include <config.h>
|
|
|
|
#include <apt-pkg/configuration.h>
|
|
#include <apt-pkg/debindexfile.h>
|
|
#include <apt-pkg/debsystem.h>
|
|
#include <apt-pkg/debversion.h>
|
|
#include <apt-pkg/dpkgpm.h>
|
|
#include <apt-pkg/error.h>
|
|
#include <apt-pkg/fileutl.h>
|
|
#include <apt-pkg/pkgcache.h>
|
|
#include <apt-pkg/progress.h>
|
|
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
|
|
#include <cctype>
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include <apti18n.h>
|
|
/*}}}*/
|
|
|
|
using std::string;
|
|
|
|
debSystem debSys;
|
|
|
|
class APT_HIDDEN debSystemPrivate {
|
|
public:
|
|
debSystemPrivate() : FrontendLockFD(-1), LockFD(-1), LockCount(0), StatusFile(0)
|
|
{
|
|
}
|
|
// For locking support
|
|
int FrontendLockFD;
|
|
int LockFD;
|
|
unsigned LockCount;
|
|
|
|
debStatusIndex *StatusFile;
|
|
};
|
|
|
|
// System::debSystem - Constructor /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* */
|
|
debSystem::debSystem() : pkgSystem("Debian dpkg interface", &debVS), d(new debSystemPrivate())
|
|
{
|
|
}
|
|
/*}}}*/
|
|
// System::~debSystem - Destructor /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* */
|
|
debSystem::~debSystem()
|
|
{
|
|
delete d->StatusFile;
|
|
delete d;
|
|
}
|
|
/*}}}*/
|
|
// System::Lock - Get the lock /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* This mirrors the operations dpkg does when it starts up. Note the
|
|
checking of the updates directory. */
|
|
static int GetLockMaybeWait(std::string const &file, OpProgress *Progress, int &timeoutSec)
|
|
{
|
|
struct ScopedAbsoluteProgress
|
|
{
|
|
ScopedAbsoluteProgress() { _config->Set("APT::Internal::OpProgress::Absolute", true); }
|
|
~ScopedAbsoluteProgress() { _config->Set("APT::Internal::OpProgress::Absolute", false); }
|
|
} _scopedAbsoluteProgress;
|
|
int fd = -1;
|
|
if (timeoutSec == 0 || Progress == nullptr)
|
|
return GetLock(file);
|
|
|
|
if (_config->FindB("Debug::Locking", false))
|
|
std::cerr << "Lock: " << file << " wait " << timeoutSec << std::endl;
|
|
|
|
for (int i = 0; timeoutSec < 0 || i < timeoutSec; i++)
|
|
{
|
|
_error->PushToStack();
|
|
fd = GetLock(file);
|
|
if (fd != -1 || errno == EPERM)
|
|
{
|
|
if (timeoutSec > 0)
|
|
timeoutSec -= i;
|
|
_error->MergeWithStack();
|
|
return fd;
|
|
}
|
|
std::string poppedError;
|
|
std::string completeError;
|
|
_error->PopMessage(poppedError);
|
|
_error->RevertToStack();
|
|
|
|
strprintf(completeError, _("Waiting for cache lock: %s"), poppedError.c_str());
|
|
sleep(1);
|
|
Progress->OverallProgress(i, timeoutSec, 0, completeError);
|
|
}
|
|
|
|
if (timeoutSec > 0)
|
|
timeoutSec = 1;
|
|
return fd;
|
|
}
|
|
|
|
bool debSystem::Lock(OpProgress *const Progress)
|
|
{
|
|
// Disable file locking
|
|
if (_config->FindB("Debug::NoLocking",false) == true || d->LockCount > 0)
|
|
{
|
|
d->LockCount++;
|
|
return true;
|
|
}
|
|
|
|
// This will count downwards.
|
|
int lockTimeOutSec = _config->FindI("DPkg::Lock::Timeout", 0);
|
|
// Create the lockfile
|
|
string AdminDir = flNotFile(_config->FindFile("Dir::State::status"));
|
|
string FrontendLockFile = AdminDir + "lock-frontend";
|
|
d->FrontendLockFD = GetLockMaybeWait(FrontendLockFile, Progress, lockTimeOutSec);
|
|
if (d->FrontendLockFD == -1)
|
|
{
|
|
if (errno == EACCES || errno == EAGAIN)
|
|
return _error->Error(_("Unable to acquire the dpkg frontend lock (%s), "
|
|
"is another process using it?"),FrontendLockFile.c_str());
|
|
else
|
|
return _error->Error(_("Unable to acquire the dpkg frontend lock (%s), "
|
|
"are you root?"),FrontendLockFile.c_str());
|
|
}
|
|
if (LockInner(Progress, lockTimeOutSec) == false)
|
|
{
|
|
close(d->FrontendLockFD);
|
|
return false;
|
|
}
|
|
|
|
// See if we need to abort with a dirty journal
|
|
if (CheckUpdates() == true)
|
|
{
|
|
close(d->LockFD);
|
|
close(d->FrontendLockFD);
|
|
d->FrontendLockFD = -1;
|
|
d->LockFD = -1;
|
|
const char *cmd;
|
|
if (getenv("SUDO_USER") != NULL)
|
|
cmd = "sudo dpkg --configure -a";
|
|
else
|
|
cmd = "dpkg --configure -a";
|
|
// TRANSLATORS: the %s contains the recovery command, usually
|
|
// dpkg --configure -a
|
|
return _error->Error(_("dpkg was interrupted, you must manually "
|
|
"run '%s' to correct the problem. "), cmd);
|
|
}
|
|
|
|
d->LockCount++;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool debSystem::LockInner(OpProgress *const Progress, int timeOutSec)
|
|
{
|
|
string AdminDir = flNotFile(_config->FindFile("Dir::State::status"));
|
|
d->LockFD = GetLockMaybeWait(AdminDir + "lock", Progress, timeOutSec);
|
|
if (d->LockFD == -1)
|
|
{
|
|
if (errno == EACCES || errno == EAGAIN)
|
|
return _error->Error(_("Unable to lock the administration directory (%s), "
|
|
"is another process using it?"),AdminDir.c_str());
|
|
else
|
|
return _error->Error(_("Unable to lock the administration directory (%s), "
|
|
"are you root?"),AdminDir.c_str());
|
|
}
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
// System::UnLock - Drop a lock /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* */
|
|
bool debSystem::UnLock(bool NoErrors)
|
|
{
|
|
if (d->LockCount == 0 && NoErrors == true)
|
|
return false;
|
|
|
|
if (d->LockCount < 1)
|
|
return _error->Error(_("Not locked"));
|
|
if (--d->LockCount == 0)
|
|
{
|
|
close(d->LockFD);
|
|
close(d->FrontendLockFD);
|
|
d->LockCount = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool debSystem::UnLockInner(bool NoErrors) {
|
|
(void) NoErrors;
|
|
close(d->LockFD);
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
// System::IsLocked - Check if system is locked /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* This checks if the frontend lock is hold. The inner lock might be
|
|
* released. */
|
|
bool debSystem::IsLocked()
|
|
{
|
|
return d->LockCount > 0;
|
|
}
|
|
/*}}}*/
|
|
// System::CheckUpdates - Check if the updates dir is dirty /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* This does a check of the updates directory (dpkg journal) to see if it has
|
|
any entries in it. */
|
|
bool debSystem::CheckUpdates()
|
|
{
|
|
// Check for updates.. (dirty)
|
|
string File = flNotFile(_config->FindFile("Dir::State::status")) + "updates/";
|
|
DIR *DirP = opendir(File.c_str());
|
|
if (DirP == 0)
|
|
return false;
|
|
|
|
/* We ignore any files that are not all digits, this skips .,.. and
|
|
some tmp files dpkg will leave behind.. */
|
|
bool Damaged = false;
|
|
for (struct dirent *Ent = readdir(DirP); Ent != 0; Ent = readdir(DirP))
|
|
{
|
|
Damaged = true;
|
|
for (unsigned int I = 0; Ent->d_name[I] != 0; I++)
|
|
{
|
|
// Check if its not a digit..
|
|
if (isdigit(Ent->d_name[I]) == 0)
|
|
{
|
|
Damaged = false;
|
|
break;
|
|
}
|
|
}
|
|
if (Damaged == true)
|
|
break;
|
|
}
|
|
closedir(DirP);
|
|
|
|
return Damaged;
|
|
}
|
|
/*}}}*/
|
|
// System::CreatePM - Create the underlying package manager /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* */
|
|
pkgPackageManager *debSystem::CreatePM(pkgDepCache *Cache) const
|
|
{
|
|
return new pkgDPkgPM(Cache);
|
|
}
|
|
/*}}}*/
|
|
// System::Initialize - Setup the configuration space.. /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* These are the Debian specific configuration variables.. */
|
|
static std::string getDpkgStatusLocation(Configuration const &Cnf) {
|
|
Configuration PathCnf;
|
|
PathCnf.Set("Dir", Cnf.Find("Dir", "/"));
|
|
PathCnf.Set("Dir::State::status", "status");
|
|
auto const cnfstatedir = Cnf.Find("Dir::State", &STATE_DIR[1]);
|
|
// if the state dir ends in apt, replace it with dpkg -
|
|
// for the default this gives us the same as the fallback below.
|
|
// This can't be a ../dpkg as that would play bad with symlinks
|
|
std::string statedir;
|
|
if (APT::String::Endswith(cnfstatedir, "/apt/"))
|
|
statedir.assign(cnfstatedir, 0, cnfstatedir.length() - 5);
|
|
else if (APT::String::Endswith(cnfstatedir, "/apt"))
|
|
statedir.assign(cnfstatedir, 0, cnfstatedir.length() - 4);
|
|
if (statedir.empty())
|
|
PathCnf.Set("Dir::State", "var/lib/dpkg");
|
|
else
|
|
PathCnf.Set("Dir::State", flCombine(statedir, "dpkg"));
|
|
return PathCnf.FindFile("Dir::State::status");
|
|
}
|
|
bool debSystem::Initialize(Configuration &Cnf)
|
|
{
|
|
/* These really should be jammed into a generic 'Local Database' engine
|
|
which is yet to be determined. The functions in pkgcachegen should
|
|
be the only users of these */
|
|
Cnf.CndSet("Dir::State::extended_states", "extended_states");
|
|
if (Cnf.Exists("Dir::State::status") == false)
|
|
Cnf.Set("Dir::State::status", getDpkgStatusLocation(Cnf));
|
|
Cnf.CndSet("Dir::Bin::dpkg",BIN_DIR"/dpkg");
|
|
|
|
if (d->StatusFile) {
|
|
delete d->StatusFile;
|
|
d->StatusFile = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
// System::ArchiveSupported - Is a file format supported /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* The standard name for a deb is 'deb'.. There are no separate versions
|
|
of .deb to worry about.. */
|
|
APT_PURE bool debSystem::ArchiveSupported(const char *Type)
|
|
{
|
|
if (strcmp(Type,"deb") == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
/*}}}*/
|
|
// System::Score - Determine how 'Debiany' this sys is.. /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* We check some files that are sure tell signs of this being a Debian
|
|
System.. */
|
|
signed debSystem::Score(Configuration const &Cnf)
|
|
{
|
|
signed Score = 0;
|
|
if (FileExists(Cnf.FindFile("Dir::State::status",getDpkgStatusLocation(Cnf).c_str())) == true)
|
|
Score += 10;
|
|
if (FileExists(Cnf.Find("Dir::Bin::dpkg",BIN_DIR"/dpkg")) == true)
|
|
Score += 10;
|
|
if (FileExists("/etc/debian_version") == true)
|
|
Score += 10;
|
|
return Score;
|
|
}
|
|
/*}}}*/
|
|
// System::AddStatusFiles - Register the status files /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* */
|
|
bool debSystem::AddStatusFiles(std::vector<pkgIndexFile *> &List)
|
|
{
|
|
if (d->StatusFile == nullptr)
|
|
{
|
|
auto dpkgstatus = _config->FindFile("Dir::State::status");
|
|
if (dpkgstatus.empty())
|
|
return true;
|
|
// we ignore only if the file doesn't exist, not if it is inaccessible
|
|
// e.g. due to permissions on parent directories as FileExists would do
|
|
errno = 0;
|
|
if (access(dpkgstatus.c_str(), R_OK) != 0 && errno == ENOENT)
|
|
return true;
|
|
_error->PushToStack();
|
|
d->StatusFile = new debStatusIndex(std::move(dpkgstatus));
|
|
bool const errored = _error->PendingError();
|
|
_error->MergeWithStack();
|
|
if (errored)
|
|
{
|
|
delete d->StatusFile;
|
|
d->StatusFile = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
List.push_back(d->StatusFile);
|
|
return true;
|
|
}
|
|
/*}}}*/
|
|
// System::FindIndex - Get an index file for status files /*{{{*/
|
|
// ---------------------------------------------------------------------
|
|
/* */
|
|
bool debSystem::FindIndex(pkgCache::PkgFileIterator File,
|
|
pkgIndexFile *&Found) const
|
|
{
|
|
if (d->StatusFile == 0)
|
|
return false;
|
|
if (d->StatusFile->FindInCache(*File.Cache()) == File)
|
|
{
|
|
Found = d->StatusFile;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
/*}}}*/
|
|
std::string debSystem::StripDpkgChrootDirectory(std::string const &File)/*{{{*/
|
|
{
|
|
// If the filename string begins with DPkg::Chroot-Directory, return the
|
|
// substr that is within the chroot so dpkg can access it.
|
|
std::string const chrootdir = _config->FindDir("DPkg::Chroot-Directory","/");
|
|
size_t len = chrootdir.length();
|
|
if (chrootdir == "/" || File.compare(0, len, chrootdir) != 0)
|
|
return File;
|
|
if (chrootdir.at(len - 1) == '/')
|
|
--len;
|
|
return File.substr(len);
|
|
}
|
|
/*}}}*/
|
|
std::string debSystem::GetDpkgExecutable() /*{{{*/
|
|
{
|
|
std::string dpkg_executable = _config->Find("Dir::Bin::dpkg","dpkg");
|
|
if (APT::String::Startswith(dpkg_executable, "/")) {
|
|
struct stat buf;
|
|
if (stat(dpkg_executable.c_str(), &buf) != 0) {
|
|
_error->Warning(_("The dpkg executable set in Dir::Bin::dpkg is "
|
|
"missing, falling back to using default dpkg."));
|
|
dpkg_executable = "dpkg";
|
|
}
|
|
}
|
|
return StripDpkgChrootDirectory(dpkg_executable);
|
|
}
|
|
/*}}}*/
|
|
std::vector<std::string> debSystem::GetDpkgBaseCommand() /*{{{*/
|
|
{
|
|
// Generate the base argument list for dpkg
|
|
std::vector<std::string> Args = { GetDpkgExecutable() };
|
|
// 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.push_back(Opts->Value);
|
|
}
|
|
}
|
|
auto status = _config->FindFile("Dir::State::status");
|
|
auto admindir = StripDpkgChrootDirectory(flNotFile(status));
|
|
if (admindir != "/var/lib/dpkg/")
|
|
{
|
|
Args.push_back("--admindir");
|
|
Args.push_back(admindir);
|
|
}
|
|
return Args;
|
|
}
|
|
/*}}}*/
|
|
void debSystem::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);
|
|
}
|
|
/*}}}*/
|
|
pid_t debSystem::ExecDpkg(std::vector<std::string> const &sArgs, int * const inputFd, int * const outputFd, bool const DiscardOutput)/*{{{*/
|
|
{
|
|
std::vector<const char *> Args(sArgs.size(), NULL);
|
|
std::transform(sArgs.begin(), sArgs.end(), Args.begin(), [](std::string const &s) { return s.c_str(); });
|
|
Args.push_back(NULL);
|
|
|
|
int external[2] = {-1, -1};
|
|
if (inputFd != nullptr || outputFd != nullptr)
|
|
if (pipe(external) != 0)
|
|
{
|
|
_error->WarningE("dpkg", "Can't create IPC pipe for dpkg call");
|
|
return -1;
|
|
}
|
|
|
|
pid_t const dpkg = ExecFork();
|
|
if (dpkg == 0) {
|
|
int const nullfd = open("/dev/null", O_RDWR);
|
|
if (inputFd == nullptr)
|
|
dup2(nullfd, STDIN_FILENO);
|
|
else
|
|
{
|
|
close(external[1]);
|
|
dup2(external[0], STDIN_FILENO);
|
|
}
|
|
if (outputFd == nullptr)
|
|
dup2(nullfd, STDOUT_FILENO);
|
|
else
|
|
{
|
|
close(external[0]);
|
|
dup2(external[1], STDOUT_FILENO);
|
|
}
|
|
if (DiscardOutput == true)
|
|
dup2(nullfd, STDERR_FILENO);
|
|
debSystem::DpkgChrootDirectory();
|
|
|
|
if (_system != nullptr && _system->IsLocked() == true)
|
|
{
|
|
setenv("DPKG_FRONTEND_LOCKED", "true", 1);
|
|
}
|
|
|
|
if (_config->Find("DPkg::Path", "").empty() == false)
|
|
setenv("PATH", _config->Find("DPkg::Path", "").c_str(), 1);
|
|
|
|
execvp(Args[0], (char**) &Args[0]);
|
|
_error->WarningE("dpkg", "Can't execute dpkg!");
|
|
_exit(100);
|
|
}
|
|
if (outputFd != nullptr)
|
|
{
|
|
close(external[1]);
|
|
*outputFd = external[0];
|
|
}
|
|
else if (inputFd != nullptr)
|
|
{
|
|
close(external[0]);
|
|
*inputFd = external[1];
|
|
}
|
|
return dpkg;
|
|
}
|
|
/*}}}*/
|
|
bool debSystem::MultiArchSupported() const /*{{{*/
|
|
{
|
|
return AssertFeature("multi-arch");
|
|
}
|
|
/*}}}*/
|
|
bool debSystem::AssertFeature(std::string const &feature) /*{{{*/
|
|
{
|
|
std::vector<std::string> Args = GetDpkgBaseCommand();
|
|
Args.push_back("--assert-" + feature);
|
|
pid_t const dpkgAssertMultiArch = ExecDpkg(Args, nullptr, nullptr, true);
|
|
if (dpkgAssertMultiArch > 0)
|
|
{
|
|
int Status = 0;
|
|
while (waitpid(dpkgAssertMultiArch, &Status, 0) != dpkgAssertMultiArch)
|
|
{
|
|
if (errno == EINTR)
|
|
continue;
|
|
_error->WarningE("dpkgGo", _("Waited for %s but it wasn't there"), "dpkg --assert-multi-arch");
|
|
break;
|
|
}
|
|
if (WIFEXITED(Status) == true && WEXITSTATUS(Status) == 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/*}}}*/
|
|
std::vector<std::string> debSystem::ArchitecturesSupported() const /*{{{*/
|
|
{
|
|
std::vector<std::string> archs;
|
|
{
|
|
string const arch = _config->Find("APT::Architecture");
|
|
if (arch.empty() == false)
|
|
archs.push_back(std::move(arch));
|
|
}
|
|
|
|
std::vector<std::string> sArgs = GetDpkgBaseCommand();
|
|
sArgs.push_back("--print-foreign-architectures");
|
|
int outputFd = -1;
|
|
pid_t const dpkgMultiArch = ExecDpkg(sArgs, nullptr, &outputFd, true);
|
|
if (dpkgMultiArch == -1)
|
|
return archs;
|
|
|
|
if(FILE *dpkg = fdopen(outputFd, "r")) {
|
|
char* buf = NULL;
|
|
size_t bufsize = 0;
|
|
DEFER([&] { fclose(dpkg); free(buf); });
|
|
while (getline(&buf, &bufsize, dpkg) != -1)
|
|
{
|
|
char* tok_saveptr;
|
|
char* arch = strtok_r(buf, " ", &tok_saveptr);
|
|
while (arch != NULL) {
|
|
for (; isspace_ascii(*arch) != 0; ++arch);
|
|
if (arch[0] != '\0') {
|
|
char const* archend = arch;
|
|
for (; isspace_ascii(*archend) == 0 && *archend != '\0'; ++archend);
|
|
string a(arch, (archend - arch));
|
|
if (std::find(archs.begin(), archs.end(), a) == archs.end())
|
|
archs.push_back(a);
|
|
}
|
|
arch = strtok_r(NULL, " ", &tok_saveptr);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
close(outputFd);
|
|
ExecWait(dpkgMultiArch, "dpkg --print-foreign-architectures", true);
|
|
return archs;
|
|
}
|
|
/*}}}*/
|