diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:00:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:00:48 +0000 |
commit | 851b6a097165af4d51c0db01b5e05256e5006896 (patch) | |
tree | 5f7c388ec894a7806c49a99f3bdb605d0b299a7c /apt-private | |
parent | Initial commit. (diff) | |
download | apt-upstream.tar.xz apt-upstream.zip |
Adding upstream version 2.6.1.upstream/2.6.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'apt-private')
41 files changed, 8321 insertions, 0 deletions
diff --git a/apt-private/CMakeLists.txt b/apt-private/CMakeLists.txt new file mode 100644 index 0000000..88a8f97 --- /dev/null +++ b/apt-private/CMakeLists.txt @@ -0,0 +1,26 @@ +# Set the version of the library +set(MAJOR 0.0) +set(MINOR 0) + +# Definition of the C++ files used to build the library - note that this +# is expanded at CMake time, so you have to rerun cmake if you add or remove +# a file (you can just run cmake . in the build directory) +file(GLOB_RECURSE library "*.cc") +file(GLOB_RECURSE headers "*.h") + +# Create a library using the C++ files +add_library(apt-private SHARED ${library}) + +# Link the library and set the SONAME +target_link_libraries(apt-private PUBLIC apt-pkg) +set_target_properties(apt-private PROPERTIES VERSION ${MAJOR}.${MINOR}) +set_target_properties(apt-private PROPERTIES SOVERSION ${MAJOR}) +set_target_properties(apt-private PROPERTIES CXX_VISIBILITY_PRESET hidden) +add_version_script(apt-private) + +# Install the library and the headers +install(TARGETS apt-private + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + NAMELINK_SKIP) + +flatify(${PROJECT_BINARY_DIR}/include/apt-private/ "${headers}") diff --git a/apt-private/acqprogress.cc b/apt-private/acqprogress.cc new file mode 100644 index 0000000..fa7edfc --- /dev/null +++ b/apt-private/acqprogress.cc @@ -0,0 +1,368 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Acquire Progress - Command line progress meter + + ##################################################################### */ + /*}}}*/ +// Include files /*{{{*/ +#include <config.h> + +#include <apt-pkg/acquire-item.h> +#include <apt-pkg/acquire-worker.h> +#include <apt-pkg/acquire.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/strutl.h> + +#include <apt-private/acqprogress.h> +#include <apt-private/private-output.h> + +#include <iostream> +#include <sstream> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ + +// AcqTextStatus::AcqTextStatus - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +AcqTextStatus::AcqTextStatus(std::ostream &out, unsigned int &ScreenWidth,unsigned int const Quiet) : + pkgAcquireStatus(), out(out), ScreenWidth(ScreenWidth), LastLineLength(0), ID(0), Quiet(Quiet) +{ + // testcases use it to disable pulses without disabling other user messages + if (Quiet == 0 && _config->FindB("quiet::NoUpdate", false) == true) + this->Quiet = 1; + if (Quiet < 2 && _config->FindB("quiet::NoProgress", false) == true) + this->Quiet = 2; +} + /*}}}*/ +// AcqTextStatus::Start - Downloading has started /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void AcqTextStatus::Start() +{ + pkgAcquireStatus::Start(); + LastLineLength = 0; + ID = 1; +} + /*}}}*/ +void AcqTextStatus::AssignItemID(pkgAcquire::ItemDesc &Itm) /*{{{*/ +{ + /* In theory calling it from Fetch() would be enough, but to be + safe we call it from IMSHit and Fail as well. + Also, an Item can pass through multiple stages, so ensure + that it keeps the same number */ + if (Itm.Owner->ID == 0) + Itm.Owner->ID = ID++; +} + /*}}}*/ +// AcqTextStatus::IMSHit - Called when an item got a HIT response /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void AcqTextStatus::IMSHit(pkgAcquire::ItemDesc &Itm) +{ + if (Quiet > 1) + return; + + AssignItemID(Itm); + clearLastLine(); + + // TRANSLATOR: Very short word to be displayed before unchanged files in 'apt-get update' + ioprintf(out, _("Hit:%lu %s"), Itm.Owner->ID, Itm.Description.c_str()); + out << std::endl; + Update = true; +} + /*}}}*/ +// AcqTextStatus::Fetch - An item has started to download /*{{{*/ +// --------------------------------------------------------------------- +/* This prints out the short description and the expected size */ +void AcqTextStatus::Fetch(pkgAcquire::ItemDesc &Itm) +{ + Update = true; + if (Itm.Owner->Complete == true) + return; + AssignItemID(Itm); + + if (Quiet > 1) + return; + + clearLastLine(); + + // TRANSLATOR: Very short word to be displayed for files processed in 'apt-get update' + // Potentially replaced later by "Hit:", "Ign:" or "Err:" if something (bad) happens + ioprintf(out, _("Get:%lu %s"), Itm.Owner->ID, Itm.Description.c_str()); + if (Itm.Owner->FileSize != 0) + out << " [" << SizeToStr(Itm.Owner->FileSize) << "B]"; + out << std::endl; +} + /*}}}*/ +// AcqTextStatus::Done - Completed a download /*{{{*/ +// --------------------------------------------------------------------- +/* We don't display anything... */ +void AcqTextStatus::Done(pkgAcquire::ItemDesc &Itm) +{ + Update = true; + AssignItemID(Itm); +} + /*}}}*/ +// AcqTextStatus::Fail - Called when an item fails to download /*{{{*/ +// --------------------------------------------------------------------- +/* We print out the error text */ +void AcqTextStatus::Fail(pkgAcquire::ItemDesc &Itm) +{ + if (Quiet > 1) + return; + + AssignItemID(Itm); + clearLastLine(); + + bool ShowErrorText = true; + if (Itm.Owner->Status == pkgAcquire::Item::StatDone || Itm.Owner->Status == pkgAcquire::Item::StatIdle) + { + // TRANSLATOR: Very short word to be displayed for files in 'apt-get update' + // which failed to download, but the error is ignored (compare "Err:") + ioprintf(out, _("Ign:%lu %s"), Itm.Owner->ID, Itm.Description.c_str()); + if (Itm.Owner->ErrorText.empty() || + _config->FindB("Acquire::Progress::Ignore::ShowErrorText", false) == false) + ShowErrorText = false; + } + else + { + // TRANSLATOR: Very short word to be displayed for files in 'apt-get update' + // which failed to download and the error is critical (compare "Ign:") + ioprintf(out, _("Err:%lu %s"), Itm.Owner->ID, Itm.Description.c_str()); + } + + if (ShowErrorText) + { + std::string::size_type line_start = 0; + std::string::size_type line_end; + while ((line_end = Itm.Owner->ErrorText.find_first_of("\n\r", line_start)) != std::string::npos) { + out << std::endl << " " << Itm.Owner->ErrorText.substr(line_start, line_end - line_start); + line_start = Itm.Owner->ErrorText.find_first_not_of("\n\r", line_end + 1); + if (line_start == std::string::npos) + break; + } + if (line_start == 0) + out << std::endl << " " << Itm.Owner->ErrorText; + else if (line_start != std::string::npos) + out << std::endl << " " << Itm.Owner->ErrorText.substr(line_start); + } + out << std::endl; + + Update = true; +} + /*}}}*/ +// AcqTextStatus::Stop - Finished downloading /*{{{*/ +// --------------------------------------------------------------------- +/* This prints out the bytes downloaded and the overall average line + speed */ +void AcqTextStatus::Stop() +{ + pkgAcquireStatus::Stop(); + if (Quiet > 1) + return; + + clearLastLine(); + + if (_config->FindB("quiet::NoStatistic", false) == true) + return; + + if (FetchedBytes != 0 && _error->PendingError() == false) + ioprintf(out,_("Fetched %sB in %s (%sB/s)\n"), + SizeToStr(FetchedBytes).c_str(), + TimeToStr(ElapsedTime).c_str(), + SizeToStr(CurrentCPS).c_str()); +} + /*}}}*/ +// AcqTextStatus::Pulse - Regular event pulse /*{{{*/ +// --------------------------------------------------------------------- +/* This draws the current progress. Each line has an overall percent + meter and a per active item status meter along with an overall + bandwidth and ETA indicator. */ +bool AcqTextStatus::Pulse(pkgAcquire *Owner) +{ + pkgAcquireStatus::Pulse(Owner); + + if (Quiet > 0) + return true; + + std::string Line; + { + std::stringstream S; + for (pkgAcquire::Worker *I = Owner->WorkersBegin(); I != 0; + I = Owner->WorkerStep(I)) + { + // There is no item running + if (I->CurrentItem == 0) + { + if (I->Status.empty() == false) + S << " [" << I->Status << "]"; + + continue; + } + + // Add in the short description + S << " ["; + if (I->CurrentItem->Owner->ID != 0) + S << std::to_string(I->CurrentItem->Owner->ID) << " "; + S << I->CurrentItem->ShortDesc; + + // Show the short mode string + if (I->CurrentItem->Owner->ActiveSubprocess.empty() == false) + S << " " << I->CurrentItem->Owner->ActiveSubprocess; + + enum {Long = 0,Medium,Short} Mode = Medium; + // Add the current progress + if (Mode == Long) + S << " " << std::to_string(I->CurrentItem->CurrentSize); + else + { + if (Mode == Medium || I->CurrentItem->TotalSize == 0) + S << " " << SizeToStr(I->CurrentItem->CurrentSize) << "B"; + } + + // Add the total size and percent + if (I->CurrentItem->TotalSize > 0 && I->CurrentItem->Owner->Complete == false) + { + if (Mode == Short) + ioprintf(S, " %.0f%%", (I->CurrentItem->CurrentSize*100.0)/I->CurrentItem->TotalSize); + else + ioprintf(S, "/%sB %.0f%%", SizeToStr(I->CurrentItem->TotalSize).c_str(), + (I->CurrentItem->CurrentSize*100.0)/I->CurrentItem->TotalSize); + } + S << "]"; + } + + // Show at least something + Line = S.str(); + S.clear(); + if (Line.empty() == true) + Line = _(" [Working]"); + } + // Put in the percent done + { + std::stringstream S; + ioprintf(S, "%.0f%%", Percent); + S << Line; + Line = S.str(); + S.clear(); + } + + /* Put in the ETA and cps meter, block off signals to prevent strangeness + during resizing */ + sigset_t Sigs,OldSigs; + sigemptyset(&Sigs); + sigaddset(&Sigs,SIGWINCH); + sigprocmask(SIG_BLOCK,&Sigs,&OldSigs); + + if (CurrentCPS != 0) + { + unsigned long long ETA = (TotalBytes - CurrentBytes)/CurrentCPS; + std::string Tmp = " " + SizeToStr(CurrentCPS) + "B/s " + TimeToStr(ETA); + size_t alignment = Line.length() + Tmp.length(); + if (alignment < ScreenWidth) + { + alignment = ScreenWidth - alignment; + for (size_t i = 0; i < alignment; ++i) + Line.append(" "); + Line.append(Tmp); + } + } + if (Line.length() > ScreenWidth) + Line.erase(ScreenWidth); + sigprocmask(SIG_SETMASK,&OldSigs,0); + + // Draw the current status + if (_config->FindB("Apt::Color", false) == true) + out << _config->Find("APT::Color::Yellow"); + if (LastLineLength > Line.length()) + clearLastLine(); + else + out << '\r'; + out << Line << std::flush; + if (_config->FindB("Apt::Color", false) == true) + out << _config->Find("APT::Color::Neutral") << std::flush; + + LastLineLength = Line.length(); + Update = false; + + return true; +} + /*}}}*/ +// AcqTextStatus::MediaChange - Media need to be swapped /*{{{*/ +// --------------------------------------------------------------------- +/* Prompt for a media swap */ +bool AcqTextStatus::MediaChange(std::string Media, std::string Drive) +{ + // If we do not output on a terminal and one of the options to avoid user + // interaction is given, we assume that no user is present who could react + // on your media change request + if (isatty(STDOUT_FILENO) != 1 && Quiet >= 2 && + (_config->FindB("APT::Get::Assume-Yes",false) == true || + _config->FindB("APT::Get::Force-Yes",false) == true || + _config->FindB("APT::Get::Trivial-Only",false) == true)) + + return false; + + clearLastLine(); + ioprintf(out,_("Media change: please insert the disc labeled\n" + " '%s'\n" + "in the drive '%s' and press [Enter]\n"), + Media.c_str(),Drive.c_str()); + + char C = 0; + bool bStatus = true; + while (C != '\n' && C != '\r') + { + int len = read(STDIN_FILENO,&C,1); + if(C == 'c' || len <= 0) { + bStatus = false; + break; + } + } + + if(bStatus) + Update = true; + return bStatus; +} + /*}}}*/ +bool AcqTextStatus::ReleaseInfoChanges(metaIndex const * const L, metaIndex const * const N, std::vector<ReleaseInfoChange> &&Changes)/*{{{*/ +{ + if (Quiet >= 2 || isatty(STDOUT_FILENO) != 1 || isatty(STDIN_FILENO) != 1 || + _config->FindB("APT::Get::Update::InteractiveReleaseInfoChanges", false) == false) + return pkgAcquireStatus::ReleaseInfoChanges(nullptr, nullptr, std::move(Changes)); + + _error->PushToStack(); + auto const confirmed = pkgAcquireStatus::ReleaseInfoChanges(L, N, std::move(Changes)); + if (confirmed == true) + { + _error->MergeWithStack(); + return true; + } + clearLastLine(); + _error->DumpErrors(out, GlobalError::NOTICE, false); + _error->RevertToStack(); + return YnPrompt(_("Do you want to accept these changes and continue updating from this repository?"), false, false, out, out); +} + /*}}}*/ +void AcqTextStatus::clearLastLine() { /*{{{*/ + if (Quiet > 0 || LastLineLength == 0) + return; + + // do not try to clear more than the (now smaller) screen + if (LastLineLength > ScreenWidth) + LastLineLength = ScreenWidth; + + out << '\r'; + for (size_t i = 0; i < LastLineLength; ++i) + out << ' '; + out << '\r' << std::flush; +} + /*}}}*/ diff --git a/apt-private/acqprogress.h b/apt-private/acqprogress.h new file mode 100644 index 0000000..87b957e --- /dev/null +++ b/apt-private/acqprogress.h @@ -0,0 +1,45 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Acquire Progress - Command line progress meter + + ##################################################################### */ + /*}}}*/ +#ifndef ACQPROGRESS_H +#define ACQPROGRESS_H + +#include <apt-pkg/acquire.h> +#include <apt-pkg/macros.h> + +#include <iostream> +#include <string> + +class APT_PUBLIC AcqTextStatus : public pkgAcquireStatus +{ + std::ostream &out; + unsigned int &ScreenWidth; + size_t LastLineLength; + unsigned long ID; + unsigned long Quiet; + + APT_HIDDEN void clearLastLine(); + APT_HIDDEN void AssignItemID(pkgAcquire::ItemDesc &Itm); + + public: + + virtual bool ReleaseInfoChanges(metaIndex const * const LastRelease, metaIndex const * const CurrentRelease, std::vector<ReleaseInfoChange> &&Changes) APT_OVERRIDE; + virtual bool MediaChange(std::string Media,std::string Drive) APT_OVERRIDE; + virtual void IMSHit(pkgAcquire::ItemDesc &Itm) APT_OVERRIDE; + virtual void Fetch(pkgAcquire::ItemDesc &Itm) APT_OVERRIDE; + virtual void Done(pkgAcquire::ItemDesc &Itm) APT_OVERRIDE; + virtual void Fail(pkgAcquire::ItemDesc &Itm) APT_OVERRIDE; + virtual void Start() APT_OVERRIDE; + virtual void Stop() APT_OVERRIDE; + + bool Pulse(pkgAcquire *Owner) APT_OVERRIDE; + + AcqTextStatus(std::ostream &out, unsigned int &ScreenWidth,unsigned int const Quiet); +}; + +#endif diff --git a/apt-private/private-cachefile.cc b/apt-private/private-cachefile.cc new file mode 100644 index 0000000..9d875b4 --- /dev/null +++ b/apt-private/private-cachefile.cc @@ -0,0 +1,118 @@ +// Include files /*{{{*/ +#include <config.h> + +#include <apt-pkg/algorithms.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/error.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/upgrade.h> + +#include <apt-private/private-cachefile.h> +#include <apt-private/private-output.h> + +#include <cstdlib> +#include <ostream> +#include <string.h> + +#include <apti18n.h> + /*}}}*/ + +using namespace std; + +static bool SortPackagesByName(pkgCache * const Owner, + map_pointer<pkgCache::Group> const A, map_pointer<pkgCache::Group> const B) +{ + if (A == 0) + return false; + if (B == 0 || A == B) + return true; + pkgCache::Group const * const GA = Owner->GrpP + A; + pkgCache::Group const * const GB = Owner->GrpP + B; + return strcmp(Owner->StrP + GA->Name, Owner->StrP + GB->Name) <= 0; +} +SortedPackageUniverse::SortedPackageUniverse(CacheFile &Cache) : + PackageUniverse{Cache}, List(Cache.UniverseList) +{ +} +void SortedPackageUniverse::LazyInit() const +{ + if (List.empty() == false) + return; + pkgCache * const Owner = data(); + // In Multi-Arch systems Grps are easier to sort than Pkgs + std::vector<map_pointer<pkgCache::Group>> GrpList; + List.reserve(Owner->Head().GroupCount); + for (pkgCache::GrpIterator I{Owner->GrpBegin()}; I.end() != true; ++I) + GrpList.emplace_back(I - Owner->GrpP); + std::stable_sort(GrpList.begin(), GrpList.end(), std::bind( &SortPackagesByName, Owner, std::placeholders::_1, std::placeholders::_2 )); + List.reserve(Owner->Head().PackageCount); + for (auto G : GrpList) + { + pkgCache::GrpIterator const Grp(*Owner, Owner->GrpP + G); + for (pkgCache::PkgIterator P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P)) + List.emplace_back(P - Owner->PkgP); + } +} +// CacheFile::CheckDeps - Open the cache file /*{{{*/ +// --------------------------------------------------------------------- +/* This routine generates the caches and then opens the dependency cache + and verifies that the system is OK. */ +bool CacheFile::CheckDeps(bool AllowBroken) +{ + bool FixBroken = _config->FindB("APT::Get::Fix-Broken",false); + + if (_error->PendingError() == true) + return false; + + // Check that the system is OK + if (DCache->DelCount() != 0 || DCache->InstCount() != 0) + return _error->Error("Internal error, non-zero counts"); + + // Apply corrections for half-installed packages + if (pkgApplyStatus(*DCache) == false) + return false; + + if (_config->FindB("APT::Get::Fix-Policy-Broken",false) == true) + { + FixBroken = true; + if ((DCache->PolicyBrokenCount() > 0)) + { + // upgrade all policy-broken packages with ForceImportantDeps=True + for (pkgCache::PkgIterator I = Cache->PkgBegin(); !I.end(); ++I) + if ((*DCache)[I].NowPolicyBroken() == true) + DCache->MarkInstall(I,true,0, false, true); + } + } + + // Nothing is broken + if (DCache->BrokenCount() == 0 || AllowBroken == true) + return true; + + // Attempt to fix broken things + if (FixBroken == true) + { + c1out << _("Correcting dependencies...") << flush; + if (pkgFixBroken(*DCache) == false || DCache->BrokenCount() != 0) + { + c1out << _(" failed.") << endl; + ShowBroken(c1out,*this,true); + + return _error->Error(_("Unable to correct dependencies")); + } + if (pkgMinimizeUpgrade(*DCache) == false) + return _error->Error(_("Unable to minimize the upgrade set")); + + c1out << _(" Done") << endl; + } + else + { + c1out << _("You might want to run 'apt --fix-broken install' to correct these.") << endl; + ShowBroken(c1out,*this,true); + return _error->Error(_("Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution).")); + } + + return true; +} + /*}}}*/ diff --git a/apt-private/private-cachefile.h b/apt-private/private-cachefile.h new file mode 100644 index 0000000..ccd4710 --- /dev/null +++ b/apt-private/private-cachefile.h @@ -0,0 +1,72 @@ +#ifndef APT_PRIVATE_CACHEFILE_H +#define APT_PRIVATE_CACHEFILE_H + +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/progress.h> +#include <apt-pkg/sourcelist.h> + +// class CacheFile - Cover class for some dependency cache functions /*{{{*/ +class APT_PUBLIC CacheFile : public pkgCacheFile +{ + public: + std::vector<map_pointer<pkgCache::Package>> UniverseList; + + bool CheckDeps(bool AllowBroken = false); + bool BuildCaches(bool WithLock = true) + { + OpTextProgress Prog(*_config); + if (pkgCacheFile::BuildCaches(&Prog,WithLock) == false) + return false; + return true; + } + bool Open(bool WithLock = true) + { + OpTextProgress Prog(*_config); + return pkgCacheFile::Open(&Prog,WithLock); + }; + bool OpenForInstall() + { + if (_config->FindB("APT::Get::Print-URIs") == true) + return Open(false); + else + return Open(true); + } +}; + /*}}}*/ + +class SortedPackageUniverse : public APT::PackageUniverse +{ + std::vector<map_pointer<pkgCache::Package>> &List; + void LazyInit() const; + +public: + explicit SortedPackageUniverse(CacheFile &Cache); + + class const_iterator : public APT::Container_iterator_base<APT::PackageContainerInterface, SortedPackageUniverse, SortedPackageUniverse::const_iterator, std::vector<map_pointer<pkgCache::Package>>::const_iterator, pkgCache::PkgIterator> + { + pkgCache * const Cache; + public: + inline pkgCache::PkgIterator getType(void) const + { + if (*_iter == 0) return pkgCache::PkgIterator(*Cache); + return pkgCache::PkgIterator(*Cache, Cache->PkgP + *_iter); + } + explicit const_iterator(pkgCache * const Owner, std::vector<map_pointer<pkgCache::Package>>::const_iterator i): + Container_iterator_base<APT::PackageContainerInterface, SortedPackageUniverse, SortedPackageUniverse::const_iterator, std::vector<map_pointer<pkgCache::Package>>::const_iterator, pkgCache::PkgIterator>(i), Cache(Owner) {} + + }; + typedef const_iterator iterator; + + const_iterator begin() const { LazyInit(); return const_iterator(data(), List.begin()); } + const_iterator end() const { LazyInit(); return const_iterator(data(), List.end()); } + const_iterator cbegin() const { LazyInit(); return const_iterator(data(), List.begin()); } + const_iterator cend() const { LazyInit(); return const_iterator(data(), List.end()); } + iterator begin() { LazyInit(); return iterator(data(), List.begin()); } + iterator end() { LazyInit(); return iterator(data(), List.end()); } +}; + +#endif diff --git a/apt-private/private-cacheset.cc b/apt-private/private-cacheset.cc new file mode 100644 index 0000000..1b2834e --- /dev/null +++ b/apt-private/private-cacheset.cc @@ -0,0 +1,505 @@ +#include <config.h> + +#include <apt-pkg/cacheset.h> +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cachefilter.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/policy.h> +#include <apt-pkg/progress.h> +#include <apt-pkg/strutl.h> + +#include <apt-private/private-cacheset.h> + +#include <stddef.h> + +#include <apti18n.h> + +bool GetLocalitySortedVersionSet(pkgCacheFile &CacheFile, /*{{{*/ + APT::VersionContainerInterface * const vci, + OpProgress * const progress) +{ + Matcher null_matcher = Matcher(); + return GetLocalitySortedVersionSet(CacheFile, vci, + null_matcher, progress); +} +bool GetLocalitySortedVersionSet(pkgCacheFile &CacheFile, + APT::VersionContainerInterface * const vci, + Matcher &matcher, + OpProgress * const progress) +{ + pkgCache * const Cache = CacheFile.GetPkgCache(); + if (unlikely(Cache == nullptr)) + return false; + if (progress != nullptr) + progress->SubProgress(Cache->Head().PackageCount, _("Sorting")); + + pkgDepCache * const DepCache = CacheFile.GetDepCache(); + if (unlikely(DepCache == nullptr)) + return false; + APT::CacheSetHelper helper(false); + + int Done=0; + + bool const insertCurrentVer = _config->FindB("APT::Cmd::Installed", false); + bool const insertUpgradable = _config->FindB("APT::Cmd::Upgradable", false); + bool const insertManualInstalled = _config->FindB("APT::Cmd::Manual-Installed", false); + + for (pkgCache::PkgIterator P = Cache->PkgBegin(); P.end() == false; ++P) + { + if (progress != NULL) + { + if (Done % 500 == 0) + progress->Progress(Done); + ++Done; + } + + // exclude virtual pkgs + if (P->VersionList == 0) + continue; + + if ((matcher)(P) == false) + continue; + + pkgDepCache::StateCache &state = (*DepCache)[P]; + if (insertCurrentVer == true) + { + if (P->CurrentVer != 0) + vci->FromPackage(vci, CacheFile, P, APT::CacheSetHelper::INSTALLED, helper); + } + else if (insertUpgradable == true) + { + if(P.CurrentVer() && state.Upgradable()) + vci->FromPackage(vci, CacheFile, P, APT::CacheSetHelper::CANDIDATE, helper); + } + else if (insertManualInstalled == true) + { + if (P.CurrentVer() && + ((*DepCache)[P].Flags & pkgCache::Flag::Auto) == false) + vci->FromPackage(vci, CacheFile, P, APT::CacheSetHelper::CANDIDATE, helper); + } + else + { + if (vci->FromPackage(vci, CacheFile, P, APT::CacheSetHelper::CANDIDATE, helper) == false) + { + // no candidate, this may happen for packages in + // dpkg "deinstall ok config-file" state - we pick the first ver + // (which should be the only one) + vci->insert(P.VersionList()); + } + } + } + if (progress != NULL) + progress->Done(); + return true; +} + /*}}}*/ + +// CacheSetHelper saving virtual packages /*{{{*/ +pkgCache::VerIterator CacheSetHelperVirtuals::canNotGetVersion( + enum CacheSetHelper::VerSelector const select, + pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg) +{ + switch (select) + { + case VERSIONNUMBER: + case RELEASE: + case INSTALLED: + case CANDIDATE: + case NEWEST: + case ALL: + virtualPkgs.insert(Pkg); + break; + case CANDANDINST: + case CANDINST: + case INSTCAND: + break; + } + return CacheSetHelper::canNotGetVersion(select, Cache, Pkg); +} +void CacheSetHelperVirtuals::canNotFindVersion( + enum CacheSetHelper::VerSelector const select, + APT::VersionContainerInterface * vci, + pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg) +{ + if (select == NEWEST || select == CANDIDATE || select == ALL) + virtualPkgs.insert(Pkg); + return CacheSetHelper::canNotFindVersion(select, vci, Cache, Pkg); +} +static pkgCache::PkgIterator canNotFindPkgName_impl(pkgCacheFile &Cache, std::string const &str) +{ + std::string pkg = str; + size_t const archfound = pkg.find_last_of(':'); + std::string arch; + if (archfound != std::string::npos) { + arch = pkg.substr(archfound+1); + pkg.erase(archfound); + if (arch == "all" || arch == "native") + arch = _config->Find("APT::Architecture"); + } + + // If we don't find 'foo:amd64' look for 'foo:amd64:any'. + // Note: we prepare for an error here as if foo:amd64 does not exist, + // but foo:amd64:any it means that this package is only referenced in a + // (architecture specific) dependency. We do not add to virtualPkgs directly + // as we can't decide from here which error message has to be printed. + // FIXME: This doesn't match 'barbarian' architectures + pkgCache::PkgIterator Pkg(Cache, 0); + std::vector<std::string> const archs = APT::Configuration::getArchitectures(); + if (archfound == std::string::npos) + { + for (auto const &a : archs) + { + Pkg = Cache.GetPkgCache()->FindPkg(pkg + ':' + a, "any"); + if (Pkg.end() == false && Pkg->ProvidesList != 0) + break; + } + if (Pkg.end() == true) + for (auto const &a : archs) + { + Pkg = Cache.GetPkgCache()->FindPkg(pkg + ':' + a, "any"); + if (Pkg.end() == false) + break; + } + } + else + { + Pkg = Cache.GetPkgCache()->FindPkg(pkg + ':' + arch, "any"); + if (Pkg.end() == true) + { + APT::CacheFilter::PackageArchitectureMatchesSpecification pams(arch); + for (auto const &a : archs) + { + if (pams(a.c_str()) == false) + continue; + Pkg = Cache.GetPkgCache()->FindPkg(pkg + ':' + a, "any"); + if (Pkg.end() == false) + break; + } + } + } + return Pkg; +} +pkgCache::PkgIterator CacheSetHelperVirtuals::canNotFindPkgName(pkgCacheFile &Cache, std::string const &str) +{ + pkgCache::PkgIterator const Pkg = canNotFindPkgName_impl(Cache, str); + if (Pkg.end()) + return APT::CacheSetHelper::canNotFindPkgName(Cache, str); + return Pkg; +} +CacheSetHelperVirtuals::CacheSetHelperVirtuals(bool const ShowErrors, GlobalError::MsgType const &ErrorType) : + CacheSetHelper{ShowErrors, ErrorType} +{} + /*}}}*/ + +// CacheSetHelperAPTGet - responsible for message telling from the CacheSets/*{{{*/ +CacheSetHelperAPTGet::CacheSetHelperAPTGet(std::ostream &pout) : + APT::CacheSetHelper{true}, out(pout) +{ + explicitlyNamed = true; +} +void CacheSetHelperAPTGet::showPackageSelection(pkgCache::PkgIterator const &pkg, enum PkgSelector const select, + std::string const &pattern) +{ + switch (select) + { + case REGEX: + showRegExSelection(pkg, pattern); + break; + case TASK: + showTaskSelection(pkg, pattern); + break; + case FNMATCH: + showFnmatchSelection(pkg, pattern); + break; + default: + APT::CacheSetHelper::showPackageSelection(pkg, select, pattern); + break; + } +} +void CacheSetHelperAPTGet::showTaskSelection(pkgCache::PkgIterator const &Pkg, std::string const &pattern) +{ + ioprintf(out, _("Note, selecting '%s' for task '%s'\n"), + Pkg.FullName(true).c_str(), pattern.c_str()); + explicitlyNamed = false; +} +void CacheSetHelperAPTGet::showFnmatchSelection(pkgCache::PkgIterator const &Pkg, std::string const &pattern) +{ + ioprintf(out, _("Note, selecting '%s' for glob '%s'\n"), + Pkg.FullName(true).c_str(), pattern.c_str()); + explicitlyNamed = false; +} +void CacheSetHelperAPTGet::showRegExSelection(pkgCache::PkgIterator const &Pkg, std::string const &pattern) +{ + ioprintf(out, _("Note, selecting '%s' for regex '%s'\n"), + Pkg.FullName(true).c_str(), pattern.c_str()); + explicitlyNamed = false; +} +void CacheSetHelperAPTGet::showVersionSelection(pkgCache::PkgIterator const &Pkg, + pkgCache::VerIterator const &Ver, enum VerSelector const select, std::string const &pattern) +{ + switch (select) + { + case VERSIONNUMBER: + if (pattern == Ver.VerStr()) + return; + /* fall through */ + case RELEASE: + selectedByRelease.push_back(make_pair(Ver, pattern)); + break; + default: + return APT::CacheSetHelper::showVersionSelection(Pkg, Ver, select, pattern); + } +} + +bool CacheSetHelperAPTGet::showVirtualPackageErrors(pkgCacheFile &Cache) +{ + if (virtualPkgs.empty() == true) + return true; + for (APT::PackageSet::const_iterator Pkg = virtualPkgs.begin(); + Pkg != virtualPkgs.end(); ++Pkg) { + if (Pkg->ProvidesList != 0) { + ioprintf(c1out,_("Package %s is a virtual package provided by:\n"), + Pkg.FullName(true).c_str()); + + pkgCache::PrvIterator I = Pkg.ProvidesList(); + unsigned short provider = 0; + for (; I.end() == false; ++I) { + pkgCache::PkgIterator const OPkg = I.OwnerPkg(); + + if (Cache[OPkg].CandidateVerIter(Cache) == I.OwnerVer()) + { + c1out << " " << OPkg.FullName(true) << ' ' << I.OwnerVer().VerStr(); + if (I->ProvideVersion != 0) + c1out << " (= " << I.ProvideVersion() << ")"; + if (Cache[OPkg].Install() == true && Cache[OPkg].NewInstall() == false) + c1out << _(" [Installed]"); + c1out << std::endl; + ++provider; + } + } + // if we found no candidate which provide this package, show non-candidates + if (provider == 0) + for (I = Pkg.ProvidesList(); I.end() == false; ++I) + { + c1out << " " << I.OwnerPkg().FullName(true) << " " << I.OwnerVer().VerStr(); + if (I->ProvideVersion != 0) + c1out << " (= " << I.ProvideVersion() << ")"; + c1out << _(" [Not candidate version]") << std::endl; + } + else + out << _("You should explicitly select one to install.") << std::endl; + } else { + ioprintf(c1out, + _("Package %s is not available, but is referred to by another package.\n" + "This may mean that the package is missing, has been obsoleted, or\n" + "is only available from another source\n"),Pkg.FullName(true).c_str()); + + std::vector<bool> Seen(Cache.GetPkgCache()->Head().PackageCount, false); + APT::PackageList pkglist; + for (pkgCache::DepIterator Dep = Pkg.RevDependsList(); + Dep.end() == false; ++Dep) { + if (Dep->Type != pkgCache::Dep::Replaces) + continue; + pkgCache::PkgIterator const DP = Dep.ParentPkg(); + if (Seen[DP->ID] == true) + continue; + Seen[DP->ID] = true; + pkglist.insert(DP); + } + ShowList(c1out, _("However the following packages replace it:"), pkglist, + &AlwaysTrue, &PrettyFullName, &EmptyString); + } + c1out << std::endl; + } + return false; +} +pkgCache::VerIterator CacheSetHelperAPTGet::canNotGetVersion(enum VerSelector const select, pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg) +{ + switch (select) + { + case NEWEST: + return canNotFindNewestVer(Cache, Pkg); + case CANDIDATE: + return canNotFindCandidateVer(Cache, Pkg); + case VERSIONNUMBER: + return canNotFindVersionNumber(Cache, Pkg, getLastVersionMatcher()); + case RELEASE: + return canNotFindVersionRelease(Cache, Pkg, getLastVersionMatcher()); + default: + return APT::CacheSetHelper::canNotGetVersion(select, Cache, Pkg); + } +} +void CacheSetHelperAPTGet::canNotFindVersion(enum VerSelector const select, APT::VersionContainerInterface * const vci, pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg) +{ + switch (select) + { + case NEWEST: + canNotFindNewestVer(Cache, Pkg); + break; + case CANDIDATE: + canNotFindCandidateVer(Cache, Pkg); + break; + default: + return APT::CacheSetHelper::canNotFindVersion(select, vci, Cache, Pkg); + } +} + +pkgCache::VerIterator CacheSetHelperAPTGet::canNotFindVersionNumber(pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg, std::string const &verstr) +{ + APT::VersionSet const verset = tryVirtualPackage(Cache, Pkg, CacheSetHelper::VERSIONNUMBER); + if (not verset.empty()) + return *(verset.begin()); + else if (ShowError) + { + auto const V = canNotGetVerFromVersionNumber(Cache, Pkg, verstr); + if (not V.end()) + return V; + virtualPkgs.insert(Pkg); + } + return pkgCache::VerIterator(Cache, 0); +} +pkgCache::VerIterator CacheSetHelperAPTGet::canNotFindVersionRelease(pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg, std::string const &verstr) +{ + APT::VersionSet const verset = tryVirtualPackage(Cache, Pkg, CacheSetHelper::RELEASE); + if (not verset.empty()) + return *(verset.begin()); + else if (ShowError) + { + auto const V = canNotGetVerFromRelease(Cache, Pkg, verstr); + if (not V.end()) + return V; + virtualPkgs.insert(Pkg); + } + return pkgCache::VerIterator(Cache, 0); +} +pkgCache::VerIterator CacheSetHelperAPTGet::canNotFindCandidateVer(pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg) +{ + APT::VersionSet const verset = tryVirtualPackage(Cache, Pkg, CacheSetHelper::CANDIDATE); + if (verset.empty() == false) + return *(verset.begin()); + else if (ShowError == true) { + _error->Error(_("Package '%s' has no installation candidate"),Pkg.FullName(true).c_str()); + virtualPkgs.insert(Pkg); + } + return pkgCache::VerIterator(Cache, 0); +} +pkgCache::VerIterator CacheSetHelperAPTGet::canNotFindNewestVer(pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg) +{ + if (Pkg->ProvidesList != 0) + { + APT::VersionSet const verset = tryVirtualPackage(Cache, Pkg, CacheSetHelper::NEWEST); + if (verset.empty() == false) + return *(verset.begin()); + if (ShowError == true) + ioprintf(out, _("Virtual packages like '%s' can't be removed\n"), Pkg.FullName(true).c_str()); + } + else + { + pkgCache::GrpIterator Grp = Pkg.Group(); + pkgCache::PkgIterator P = Grp.PackageList(); + for (; P.end() != true; P = Grp.NextPkg(P)) + { + if (P == Pkg) + continue; + if (P->CurrentVer != 0) { + // TRANSLATORS: Note, this is not an interactive question + ioprintf(c1out,_("Package '%s' is not installed, so not removed. Did you mean '%s'?\n"), + Pkg.FullName(true).c_str(), P.FullName(true).c_str()); + break; + } + } + if (P.end() == true) + ioprintf(c1out,_("Package '%s' is not installed, so not removed\n"),Pkg.FullName(true).c_str()); + } + return pkgCache::VerIterator(Cache, 0); +} +APT::VersionSet CacheSetHelperAPTGet::tryVirtualPackage(pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg, + CacheSetHelper::VerSelector const select) +{ + /* If this is a virtual package see if we have a single matching provider + (ignoring multiple matches from the same package due to e.g. M-A) */ + if (Pkg->ProvidesList == 0) + return APT::VersionSet{}; + + auto const oldShowError = showErrors(false); + APT::VersionVector verset; + auto const lastmatcher = getLastVersionMatcher(); + for (auto P = Pkg.ProvidesList(); not P.end(); ++P) + { + auto V = P.OwnerVer(); + switch (select) + { + case RELEASE: + for (auto File = V.FileList(); not File.end(); ++File) + if ((File.File().Archive() != nullptr && lastmatcher == File.File().Archive()) || + (File.File().Codename() != nullptr && lastmatcher == File.File().Codename())) + { + verset.push_back(V); + break; + } + break; + case VERSIONNUMBER: + if (P->ProvideVersion != 0 && lastmatcher == P.ProvideVersion()) + verset.push_back(V); + break; + default: + if (Cache[V.ParentPkg()].CandidateVerIter(Cache) == V) + verset.push_back(V); + break; + } + } + // do not change the candidate if we have more than one option for this package + if (select == VERSIONNUMBER || select == RELEASE) + for (auto const &V : verset) + if (std::count_if(verset.begin(), verset.end(), [Pkg = V.ParentPkg()](auto const &v) { return v.ParentPkg() == Pkg; }) == 1) + Cache->SetCandidateVersion(V); + showErrors(oldShowError); + + pkgCache::VerIterator Choosen; + for (auto const &Ver : verset) + { + if (Choosen.end()) + Choosen = Ver; + else + { + auto const ChoosenPkg = Choosen.ParentPkg(); + auto const AltPkg = Ver.ParentPkg(); + // seeing two different packages makes it not simple anymore + if (ChoosenPkg->Group != AltPkg->Group) + return APT::VersionSet{}; + // do we already have the requested arch? + if (strcmp(Pkg.Arch(), ChoosenPkg.Arch()) == 0 || + strcmp(ChoosenPkg.Arch(), "all") == 0) + continue; + // see which architecture we prefer more and switch to it + std::vector<std::string> archs = APT::Configuration::getArchitectures(); + if (std::find(archs.begin(), archs.end(), AltPkg.Arch()) < std::find(archs.begin(), archs.end(), ChoosenPkg.Arch())) + Choosen = Ver; + } + } + if (Choosen.end()) + return APT::VersionSet{}; + + ioprintf(out, _("Note, selecting '%s' instead of '%s'\n"), + Choosen.ParentPkg().FullName(true).c_str(), Pkg.FullName(true).c_str()); + return { Choosen }; +} +pkgCache::PkgIterator CacheSetHelperAPTGet::canNotFindPkgName(pkgCacheFile &Cache, std::string const &str) +{ + pkgCache::PkgIterator Pkg = canNotFindPkgName_impl(Cache, str); + if (Pkg.end()) + { + Pkg = APT::CacheSetHelper::canNotFindPkgName(Cache, str); + if (Pkg.end() && ShowError) + { + notFound.insert(str); + } + } + return Pkg; +} + /*}}}*/ diff --git a/apt-private/private-cacheset.h b/apt-private/private-cacheset.h new file mode 100644 index 0000000..e8dba5a --- /dev/null +++ b/apt-private/private-cacheset.h @@ -0,0 +1,132 @@ +#ifndef APT_PRIVATE_CACHESET_H +#define APT_PRIVATE_CACHESET_H + +#include <apt-pkg/cacheset.h> +#include <apt-pkg/macros.h> + +#include <apt-private/private-output.h> + +#include <list> +#include <set> +#include <string> +#include <vector> + +class OpProgress; + +class VerIteratorWithCaching +{ + const pkgCache::VerIterator iter; + const pkgCache::DescFile * descFile; +public: + + // cppcheck-suppress noExplicitConstructor + VerIteratorWithCaching(const pkgCache::VerIterator& iter) : + iter(iter), + descFile(iter->DescriptionList != 0 + ? (const pkgCache::DescFile *) iter.TranslatedDescription().FileList() + : nullptr) + {} + const pkgCache::DescFile * CachedDescFile() const { return descFile; } + operator pkgCache::VerIterator() const { return iter; } + map_id_t ID() const { return iter->ID; } +}; + +struct VersionSortDescriptionLocality /*{{{*/ +{ + bool operator () (const VerIteratorWithCaching &v_lhs, + const VerIteratorWithCaching &v_rhs) const + { + pkgCache::DescFile const *A = v_lhs.CachedDescFile(); + pkgCache::DescFile const *B = v_rhs.CachedDescFile(); + + if (A == nullptr) + { + if (B == nullptr) + return v_lhs.ID() < v_rhs.ID(); + return true; + } + else if (B == nullptr) + return false; + + if (A->File == B->File) + { + if (A->Offset == B->Offset) + return v_lhs.ID() < v_rhs.ID(); + return A->Offset < B->Offset; + } + + return A->File < B->File; + } +}; + /*}}}*/ +// sorted by locality which makes iterating much faster +typedef APT::VersionContainer< + std::set<VerIteratorWithCaching, + VersionSortDescriptionLocality> > LocalitySortedVersionSet; + +class Matcher { +public: + virtual bool operator () (const pkgCache::PkgIterator &/*P*/) { + return true;} +}; + +// FIXME: add default argument for OpProgress (or overloaded function) +bool GetLocalitySortedVersionSet(pkgCacheFile &CacheFile, + APT::VersionContainerInterface * const vci, + Matcher &matcher, + OpProgress * const progress); +bool GetLocalitySortedVersionSet(pkgCacheFile &CacheFile, + APT::VersionContainerInterface * const vci, + OpProgress * const progress); + + +// CacheSetHelper saving virtual packages /*{{{*/ +class CacheSetHelperVirtuals: public APT::CacheSetHelper { +public: + APT::PackageSet virtualPkgs; + + virtual pkgCache::VerIterator canNotGetVersion(enum CacheSetHelper::VerSelector const select, pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual void canNotFindVersion(enum CacheSetHelper::VerSelector const select, APT::VersionContainerInterface * vci, pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual pkgCache::PkgIterator canNotFindPkgName(pkgCacheFile &Cache, std::string const &str) APT_OVERRIDE; + + CacheSetHelperVirtuals(bool const ShowErrors = true, GlobalError::MsgType const &ErrorType = GlobalError::NOTICE); +}; + /*}}}*/ + +// CacheSetHelperAPTGet - responsible for message telling from the CacheSets/*{{{*/ +class CacheSetHelperAPTGet : public APT::CacheSetHelper { + /** \brief stream message should be printed to */ + std::ostream &out; + /** \brief were things like Task or RegEx used to select packages? */ + bool explicitlyNamed; + + APT::PackageSet virtualPkgs; +public: + std::list<std::pair<pkgCache::VerIterator, std::string> > selectedByRelease; + std::set<std::string> notFound; + + explicit CacheSetHelperAPTGet(std::ostream &out); + + virtual void showPackageSelection(pkgCache::PkgIterator const &Pkg, enum PkgSelector const select, std::string const &pattern) APT_OVERRIDE; + void showTaskSelection(pkgCache::PkgIterator const &Pkg, std::string const &pattern); + void showFnmatchSelection(pkgCache::PkgIterator const &Pkg, std::string const &pattern); + void showRegExSelection(pkgCache::PkgIterator const &Pkg, std::string const &pattern); + void showVersionSelection(pkgCache::PkgIterator const &Pkg, pkgCache::VerIterator const &Ver, enum VerSelector const select, std::string const &pattern) APT_OVERRIDE; + bool showVirtualPackageErrors(pkgCacheFile &Cache); + + pkgCache::VerIterator canNotGetVersion(enum VerSelector const select, pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + void canNotFindVersion(enum VerSelector const select, APT::VersionContainerInterface * const vci, pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + pkgCache::VerIterator canNotFindCandidateVer(pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg); + pkgCache::VerIterator canNotFindNewestVer(pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg); + pkgCache::VerIterator canNotFindVersionNumber(pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg, std::string const &verstr); + pkgCache::VerIterator canNotFindVersionRelease(pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg, std::string const &verstr); + virtual pkgCache::PkgIterator canNotFindPkgName(pkgCacheFile &Cache, std::string const &str) APT_OVERRIDE; + + APT::VersionSet tryVirtualPackage(pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg, + CacheSetHelper::VerSelector const select); + + inline bool allPkgNamedExplicitly() const { return explicitlyNamed; } +}; + /*}}}*/ + +#endif diff --git a/apt-private/private-cmndline.cc b/apt-private/private-cmndline.cc new file mode 100644 index 0000000..f607c4c --- /dev/null +++ b/apt-private/private-cmndline.cc @@ -0,0 +1,601 @@ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/init.h> +#include <apt-pkg/pkgsystem.h> +#include <apt-pkg/strutl.h> + +#include <apt-private/private-cmndline.h> +#include <apt-private/private-main.h> + +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <algorithm> +#include <iomanip> +#include <vector> + +#include <apti18n.h> + /*}}}*/ + +APT_NONNULL(1, 2) +static bool CmdMatches_fn(char const *const Cmd, char const *const Match) +{ + return strcmp(Cmd, Match) == 0; +} +template <typename... Tail> +APT_NONNULL(1, 2) +static bool CmdMatches_fn(char const *const Cmd, char const *const Match, Tail... MoreMatches) +{ + return CmdMatches_fn(Cmd, Match) || CmdMatches_fn(Cmd, MoreMatches...); +} +#define addArg(w, x, y, z) Args.emplace_back(CommandLine::MakeArgs(w, x, y, z)) +#define CmdMatches(...) (Cmd != nullptr && CmdMatches_fn(Cmd, __VA_ARGS__)) + +static bool addArgumentsAPTCache(std::vector<CommandLine::Args> &Args, char const * const Cmd)/*{{{*/ +{ + if (CmdMatches("depends", "rdepends")) + { + addArg('i', "important", "APT::Cache::Important", 0); + addArg(0, "installed", "APT::Cache::Installed", 0); + addArg(0, "pre-depends", "APT::Cache::ShowPre-Depends", 0); + addArg(0, "depends", "APT::Cache::ShowDepends", 0); + addArg(0, "recommends", "APT::Cache::ShowRecommends", 0); + addArg(0, "suggests", "APT::Cache::ShowSuggests", 0); + addArg(0, "replaces", "APT::Cache::ShowReplaces", 0); + addArg(0, "breaks", "APT::Cache::ShowBreaks", 0); + addArg(0, "conflicts", "APT::Cache::ShowConflicts", 0); + addArg(0, "enhances", "APT::Cache::ShowEnhances", 0); + addArg(0, "recurse", "APT::Cache::RecurseDepends", 0); + addArg(0, "implicit", "APT::Cache::ShowImplicit", 0); + } + else if (CmdMatches("search")) + { + addArg('n', "names-only", "APT::Cache::NamesOnly", 0); + addArg('f', "full", "APT::Cache::ShowFull", 0); + } + else if (CmdMatches("show") | CmdMatches("info")) + { + addArg('a', "all-versions", "APT::Cache::AllVersions", 0); + } + else if (CmdMatches("pkgnames")) + { + addArg(0, "all-names", "APT::Cache::AllNames", 0); + } + else if (CmdMatches("unmet")) + { + addArg('i', "important", "APT::Cache::Important", 0); + } + else if (CmdMatches("showsrc")) + { + addArg(0,"only-source","APT::Cache::Only-Source",0); + } + else if (CmdMatches("gencaches", "showpkg", "stats", "dump", + "dumpavail", "showauto", "policy", "madison")) + ; + else + return false; + + bool const found_something = Args.empty() == false; + + // FIXME: move to the correct command(s) + addArg('g', "generate", "APT::Cache::Generate", 0); + addArg('t', "target-release", "APT::Default-Release", CommandLine::HasArg); + addArg('t', "default-release", "APT::Default-Release", CommandLine::HasArg); + + addArg('p', "pkg-cache", "Dir::Cache::pkgcache", CommandLine::HasArg); + addArg('s', "src-cache", "Dir::Cache::srcpkgcache", CommandLine::HasArg); + addArg(0, "with-source", "APT::Sources::With::", CommandLine::HasArg); + + return found_something; +} + /*}}}*/ +static bool addArgumentsAPTCDROM(std::vector<CommandLine::Args> &Args, char const * const Cmd)/*{{{*/ +{ + if (CmdMatches("add", "ident") == false) + return false; + + // FIXME: move to the correct command(s) + addArg(0, "auto-detect", "Acquire::cdrom::AutoDetect", CommandLine::Boolean); + addArg('d', "cdrom", "Acquire::cdrom::mount", CommandLine::HasArg); + addArg('r', "rename", "APT::CDROM::Rename", 0); + addArg('m', "no-mount", "APT::CDROM::NoMount", 0); + addArg('f', "fast", "APT::CDROM::Fast", 0); + addArg('n', "just-print", "APT::CDROM::NoAct", 0); + addArg('n', "recon", "APT::CDROM::NoAct", 0); + addArg('n', "no-act", "APT::CDROM::NoAct", 0); + addArg('a', "thorough", "APT::CDROM::Thorough", 0); + return true; +} + /*}}}*/ +static bool addArgumentsAPTConfig(std::vector<CommandLine::Args> &Args, char const * const Cmd)/*{{{*/ +{ + if (CmdMatches("dump")) + { + addArg(0,"empty","APT::Config::Dump::EmptyValue",CommandLine::Boolean); + addArg(0,"format","APT::Config::Dump::Format",CommandLine::HasArg); + } + else if (CmdMatches("shell")) + ; + else + return false; + + return true; +} + /*}}}*/ +static bool addArgumentsAPTDumpSolver(std::vector<CommandLine::Args> &Args, char const * const)/*{{{*/ +{ + addArg(0,"user","APT::Solver::RunAsUser",CommandLine::HasArg); + return true; +} + /*}}}*/ +static bool addArgumentsAPTExtractTemplates(std::vector<CommandLine::Args> &Args, char const * const)/*{{{*/ +{ + addArg('t',"tempdir","APT::ExtractTemplates::TempDir",CommandLine::HasArg); + return true; +} + /*}}}*/ +static bool addArgumentsAPTFTPArchive(std::vector<CommandLine::Args> &Args, char const * const)/*{{{*/ +{ + addArg(0,"md5","APT::FTPArchive::MD5",0); + addArg(0,"sha1","APT::FTPArchive::SHA1",0); + addArg(0,"sha256","APT::FTPArchive::SHA256",0); + addArg(0,"sha512","APT::FTPArchive::SHA512",0); + addArg('d',"db","APT::FTPArchive::DB",CommandLine::HasArg); + addArg('s',"source-override","APT::FTPArchive::SourceOverride",CommandLine::HasArg); + addArg(0,"delink","APT::FTPArchive::DeLinkAct",0); + addArg(0,"readonly","APT::FTPArchive::ReadOnlyDB",0); + addArg(0,"contents","APT::FTPArchive::Contents",0); + addArg('a',"arch","APT::FTPArchive::Architecture",CommandLine::HasArg); + return true; +} + /*}}}*/ +static bool addArgumentsAPTInternalPlanner(std::vector<CommandLine::Args> &, char const * const)/*{{{*/ +{ + return true; +} + /*}}}*/ +static bool addArgumentsAPTInternalSolver(std::vector<CommandLine::Args> &, char const * const)/*{{{*/ +{ + return true; +} + /*}}}*/ +static bool addArgumentsAPTHelper(std::vector<CommandLine::Args> &Args, char const * const Cmd)/*{{{*/ +{ + if (CmdMatches("cat-file")) + { + addArg('C', "compress", "Apt-Helper::Cat-File::Compress",CommandLine::HasArg); + } + return true; +} + /*}}}*/ +static bool addArgumentsAPTGet(std::vector<CommandLine::Args> &Args, char const * const Cmd)/*{{{*/ +{ + if (CmdMatches("install", "reinstall", "remove", "purge", "upgrade", "dist-upgrade", + "dselect-upgrade", "autoremove", "autopurge", "full-upgrade")) + { + addArg(0, "show-progress", "DpkgPM::Progress", 0); + addArg('f', "fix-broken", "APT::Get::Fix-Broken", 0); + addArg(0, "purge", "APT::Get::Purge", 0); + addArg('V',"verbose-versions","APT::Get::Show-Versions",0); + addArg(0, "autoremove", "APT::Get::AutomaticRemove", 0); + addArg(0, "auto-remove", "APT::Get::AutomaticRemove", 0); + addArg(0, "reinstall", "APT::Get::ReInstall", 0); + addArg(0, "solver", "APT::Solver", CommandLine::HasArg); + addArg(0, "planner", "APT::Planner", CommandLine::HasArg); + if (CmdMatches("upgrade")) + { + addArg(0, "new-pkgs", "APT::Get::Upgrade-Allow-New", + CommandLine::Boolean); + } + } + else if (CmdMatches("update")) + { + addArg(0, "list-cleanup", "APT::Get::List-Cleanup", 0); + addArg(0, "allow-insecure-repositories", "Acquire::AllowInsecureRepositories", 0); + addArg(0, "allow-weak-repositories", "Acquire::AllowWeakRepositories", 0); + addArg(0, "allow-releaseinfo-change", "Acquire::AllowReleaseInfoChange", 0); + addArg(0, "allow-releaseinfo-change-origin", "Acquire::AllowReleaseInfoChange::Origin", 0); + addArg(0, "allow-releaseinfo-change-label", "Acquire::AllowReleaseInfoChange::Label", 0); + addArg(0, "allow-releaseinfo-change-version", "Acquire::AllowReleaseInfoChange::Version", 0); + addArg(0, "allow-releaseinfo-change-codename", "Acquire::AllowReleaseInfoChange::Codename", 0); + addArg(0, "allow-releaseinfo-change-suite", "Acquire::AllowReleaseInfoChange::Suite", 0); + addArg(0, "allow-releaseinfo-change-defaultpin", "Acquire::AllowReleaseInfoChange::DefaultPin", 0); + addArg('e', "error-on", "APT::Update::Error-Mode", CommandLine::HasArg); + } + else if (CmdMatches("source")) + { + addArg('b', "compile", "APT::Get::Compile", 0); + addArg('b', "build", "APT::Get::Compile", 0); + addArg('P', "build-profiles", "APT::Build-Profiles", CommandLine::HasArg); + addArg(0, "diff-only", "APT::Get::Diff-Only", 0); + addArg(0, "debian-only", "APT::Get::Diff-Only", 0); + addArg(0, "tar-only", "APT::Get::Tar-Only", 0); + addArg(0, "dsc-only", "APT::Get::Dsc-Only", 0); + } + else if (CmdMatches("build-dep") || CmdMatches("satisfy")) + { + addArg('a', "host-architecture", "APT::Get::Host-Architecture", CommandLine::HasArg); + addArg('P', "build-profiles", "APT::Build-Profiles", CommandLine::HasArg); + addArg(0, "purge", "APT::Get::Purge", 0); + addArg(0, "solver", "APT::Solver", CommandLine::HasArg); + if (CmdMatches("build-dep")) + { + addArg(0,"arch-only","APT::Get::Arch-Only",0); + addArg(0,"indep-only","APT::Get::Indep-Only",0); + } + // this has no effect *but* sbuild is using it (see LP: #1255806) + // once sbuild is fixed, this option can be removed + addArg('f', "fix-broken", "APT::Get::Fix-Broken", 0); + } + else if (CmdMatches("indextargets")) + { + addArg(0,"format","APT::Get::IndexTargets::Format", CommandLine::HasArg); + addArg(0,"release-info","APT::Get::IndexTargets::ReleaseInfo", 0); + } + else if (CmdMatches("clean", "autoclean", "auto-clean", "check", "download", "changelog") || + CmdMatches("markauto", "unmarkauto")) // deprecated commands + ; + else if (CmdMatches("moo")) + addArg(0, "color", "APT::Moo::Color", 0); + + if (CmdMatches("install", "reinstall", "remove", "purge", "upgrade", "dist-upgrade", + "dselect-upgrade", "autoremove", "auto-remove", "autopurge", "clean", "autoclean", "auto-clean", "check", + "build-dep", "satisfy", "full-upgrade", "source")) + { + addArg('s', "simulate", "APT::Get::Simulate", 0); + addArg('s', "just-print", "APT::Get::Simulate", 0); + addArg('s', "recon", "APT::Get::Simulate", 0); + addArg('s', "dry-run", "APT::Get::Simulate", 0); + addArg('s', "no-act", "APT::Get::Simulate", 0); + } + + bool const found_something = Args.empty() == false; + + // FIXME: move to the correct command(s) + addArg('d',"download-only","APT::Get::Download-Only",0); + addArg('y',"yes","APT::Get::Assume-Yes",0); + addArg('y',"assume-yes","APT::Get::Assume-Yes",0); + addArg(0,"assume-no","APT::Get::Assume-No",0); + addArg('u',"show-upgraded","APT::Get::Show-Upgraded",0); + addArg('m',"ignore-missing","APT::Get::Fix-Missing",0); + addArg('t',"target-release","APT::Default-Release",CommandLine::HasArg); + addArg('t',"default-release","APT::Default-Release",CommandLine::HasArg); + addArg(0,"download","APT::Get::Download",0); + addArg(0,"fix-missing","APT::Get::Fix-Missing",0); + addArg(0,"ignore-hold","APT::Ignore-Hold",0); + addArg(0,"upgrade","APT::Get::upgrade",0); + addArg(0,"only-upgrade","APT::Get::Only-Upgrade",0); + addArg(0,"allow-change-held-packages","APT::Get::allow-change-held-packages",CommandLine::Boolean); + addArg(0,"allow-remove-essential","APT::Get::allow-remove-essential",CommandLine::Boolean); + addArg(0,"allow-downgrades","APT::Get::allow-downgrades",CommandLine::Boolean); + addArg(0,"force-yes","APT::Get::force-yes",0); + addArg(0,"print-uris","APT::Get::Print-URIs",0); + addArg(0,"trivial-only","APT::Get::Trivial-Only",0); + addArg(0,"mark-auto","APT::Get::Mark-Auto",0); + addArg(0,"remove","APT::Get::Remove",0); + addArg(0,"only-source","APT::Get::Only-Source",0); + addArg(0,"allow-unauthenticated","APT::Get::AllowUnauthenticated",0); + addArg(0,"install-recommends","APT::Install-Recommends",CommandLine::Boolean); + addArg(0,"install-suggests","APT::Install-Suggests",CommandLine::Boolean); + addArg(0,"fix-policy","APT::Get::Fix-Policy-Broken",0); + addArg(0, "with-source", "APT::Sources::With::", CommandLine::HasArg); + + return found_something; +} + /*}}}*/ +static bool addArgumentsAPTMark(std::vector<CommandLine::Args> &Args, char const * const Cmd)/*{{{*/ +{ + if (CmdMatches("auto", "manual", "hold", "unhold", "showauto", + "showmanual", "showhold", "showholds", "showheld", + "markauto", "unmarkauto", "minimize-manual")) + { + addArg('f',"file","Dir::State::extended_states",CommandLine::HasArg); + } + else if (CmdMatches("install", "reinstall", "remove", "deinstall", "purge", + "showinstall", "showinstalls", "showremove", "showremoves", + "showdeinstall", "showdeinstalls", "showpurge", "showpurges")) + ; + else + return false; + + if (CmdMatches("markauto", "unmarkauto")) + { + addArg('v',"verbose","APT::MarkAuto::Verbose",0); + } + + if (CmdMatches("minimize-manual")) + { + addArg('y',"yes","APT::Get::Assume-Yes",0); + addArg('y',"assume-yes","APT::Get::Assume-Yes",0); + addArg(0,"assume-no","APT::Get::Assume-No",0); + } + + if (CmdMatches("minimize-manual") || (Cmd != nullptr && strncmp(Cmd, "show", strlen("show")) != 0)) + { + addArg('s',"simulate","APT::Mark::Simulate",0); + addArg('s',"just-print","APT::Mark::Simulate",0); + addArg('s',"recon","APT::Mark::Simulate",0); + addArg('s',"dry-run","APT::Mark::Simulate",0); + addArg('s',"no-act","APT::Mark::Simulate",0); + } + addArg(0, "with-source", "APT::Sources::With::", CommandLine::HasArg); + + return true; +} + /*}}}*/ +static bool addArgumentsAPTSortPkgs(std::vector<CommandLine::Args> &Args, char const * const)/*{{{*/ +{ + addArg('s',"source","APT::SortPkgs::Source",0); + return true; +} + /*}}}*/ +static bool addArgumentsAPT(std::vector<CommandLine::Args> &Args, char const * const Cmd)/*{{{*/ +{ + if (CmdMatches("list")) + { + addArg('i',"installed","APT::Cmd::Installed",0); + addArg(0,"upgradeable","APT::Cmd::Upgradable",0); + addArg('u',"upgradable","APT::Cmd::Upgradable",0); + addArg(0,"manual-installed","APT::Cmd::Manual-Installed",0); + addArg('v', "verbose", "APT::Cmd::List-Include-Summary", 0); + addArg('a', "all-versions", "APT::Cmd::All-Versions", 0); + } + else if (CmdMatches("show") || CmdMatches("info")) + { + addArg('a', "all-versions", "APT::Cache::AllVersions", 0); + addArg('f', "full", "APT::Cache::ShowFull", 0); + } + else if (addArgumentsAPTGet(Args, Cmd) || addArgumentsAPTCache(Args, Cmd)) + { + // we have no (supported) command-name overlaps so far, so we call + // specifics in order until we find one which adds arguments + } + else + return false; + + addArg(0, "with-source", "APT::Sources::With::", CommandLine::HasArg); + + return true; +} + /*}}}*/ +static bool addArgumentsRred(std::vector<CommandLine::Args> &Args, char const * const /*Cmd*/)/*{{{*/ +{ + addArg('t', nullptr, "Rred::T", 0); + addArg('f', nullptr, "Rred::F", 0); + addArg('C', "compress", "Rred::Compress",CommandLine::HasArg); + return true; +} + /*}}}*/ +std::vector<CommandLine::Args> getCommandArgs(APT_CMD const Program, char const * const Cmd)/*{{{*/ +{ + std::vector<CommandLine::Args> Args; + Args.reserve(50); + if (Cmd != nullptr && strcmp(Cmd, "help") == 0) + ; // no options for help so no need to implement it in each + else + switch (Program) + { + case APT_CMD::APT: addArgumentsAPT(Args, Cmd); break; + case APT_CMD::APT_GET: addArgumentsAPTGet(Args, Cmd); break; + case APT_CMD::APT_CACHE: addArgumentsAPTCache(Args, Cmd); break; + case APT_CMD::APT_CDROM: addArgumentsAPTCDROM(Args, Cmd); break; + case APT_CMD::APT_CONFIG: addArgumentsAPTConfig(Args, Cmd); break; + case APT_CMD::APT_DUMP_SOLVER: addArgumentsAPTDumpSolver(Args, Cmd); break; + case APT_CMD::APT_EXTRACTTEMPLATES: addArgumentsAPTExtractTemplates(Args, Cmd); break; + case APT_CMD::APT_FTPARCHIVE: addArgumentsAPTFTPArchive(Args, Cmd); break; + case APT_CMD::APT_HELPER: addArgumentsAPTHelper(Args, Cmd); break; + case APT_CMD::APT_INTERNAL_PLANNER: addArgumentsAPTInternalPlanner(Args, Cmd); break; + case APT_CMD::APT_INTERNAL_SOLVER: addArgumentsAPTInternalSolver(Args, Cmd); break; + case APT_CMD::APT_MARK: addArgumentsAPTMark(Args, Cmd); break; + case APT_CMD::APT_SORTPKG: addArgumentsAPTSortPkgs(Args, Cmd); break; + case APT_CMD::RRED: addArgumentsRred(Args, Cmd); break; + } + + // options without a command + addArg('h', "help", "help", 0); + addArg('v', "version", "version", 0); + // general options + addArg('q', "quiet", "quiet", CommandLine::IntLevel); + addArg('q', "silent", "quiet", CommandLine::IntLevel); + addArg('c', "config-file", 0, CommandLine::ConfigFile); + addArg('o', "option", 0, CommandLine::ArbItem); + addArg(0, NULL, NULL, 0); + + return Args; +} + /*}}}*/ +#undef addArg +static void ShowHelpListCommands(std::vector<aptDispatchWithHelp> const &Cmds)/*{{{*/ +{ + if (Cmds.empty() || Cmds[0].Match == nullptr) + return; + std::cout << std::endl << _("Most used commands:") << std::endl; + for (auto const &c: Cmds) + { + if (c.Help == nullptr) + continue; + std::cout << " " << c.Match << " - " << c.Help << std::endl; + } +} + /*}}}*/ +static bool ShowCommonHelp(APT_CMD const Binary, CommandLine &CmdL, std::vector<aptDispatchWithHelp> const &Cmds,/*{{{*/ + bool (*ShowHelp)(CommandLine &)) +{ + std::cout << PACKAGE << " " << PACKAGE_VERSION << " (" << COMMON_ARCH << ")" << std::endl; + if (_config->FindB("version") == true && Binary != APT_CMD::APT_GET) + return true; + if (ShowHelp(CmdL) == false) + return false; + if (_config->FindB("version") == true || Binary == APT_CMD::APT_FTPARCHIVE) + return true; + ShowHelpListCommands(Cmds); + std::cout << std::endl; + char const * cmd = nullptr; + switch (Binary) + { + case APT_CMD::APT: cmd = "apt(8)"; break; + case APT_CMD::APT_CACHE: cmd = "apt-cache(8)"; break; + case APT_CMD::APT_CDROM: cmd = "apt-cdrom(8)"; break; + case APT_CMD::APT_CONFIG: cmd = "apt-config(8)"; break; + case APT_CMD::APT_DUMP_SOLVER: cmd = nullptr; break; + case APT_CMD::APT_EXTRACTTEMPLATES: cmd = "apt-extracttemplates(1)"; break; + case APT_CMD::APT_FTPARCHIVE: cmd = "apt-ftparchive(1)"; break; + case APT_CMD::APT_GET: cmd = "apt-get(8)"; break; + case APT_CMD::APT_HELPER: cmd = nullptr; break; + case APT_CMD::APT_INTERNAL_PLANNER: cmd = nullptr; break; + case APT_CMD::APT_INTERNAL_SOLVER: cmd = nullptr; break; + case APT_CMD::APT_MARK: cmd = "apt-mark(8)"; break; + case APT_CMD::APT_SORTPKG: cmd = "apt-sortpkgs(1)"; break; + case APT_CMD::RRED: cmd = nullptr; break; + } + if (cmd != nullptr) + ioprintf(std::cout, _("See %s for more information about the available commands."), cmd); + if (Binary != APT_CMD::APT_DUMP_SOLVER && Binary != APT_CMD::APT_INTERNAL_SOLVER && + Binary != APT_CMD::APT_INTERNAL_PLANNER && Binary != APT_CMD::RRED) + std::cout << std::endl << + _("Configuration options and syntax is detailed in apt.conf(5).\n" + "Information about how to configure sources can be found in sources.list(5).\n" + "Package and version choices can be expressed via apt_preferences(5).\n" + "Security details are available in apt-secure(8).\n"); + if (Binary == APT_CMD::APT_GET || Binary == APT_CMD::APT) + std::cout << std::right << std::setw(70) << _("This APT has Super Cow Powers.") << std::endl; + else if (Binary == APT_CMD::APT_HELPER || Binary == APT_CMD::APT_DUMP_SOLVER) + std::cout << std::right << std::setw(70) << _("This APT helper has Super Meep Powers.") << std::endl; + return true; +} + /*}}}*/ +static void BinarySpecificConfiguration(char const * const Binary) /*{{{*/ +{ + std::string const binary = flNotDir(Binary); + if (binary == "apt" || binary == "apt-config") + { + if (getenv("NO_COLOR") == nullptr) + _config->CndSet("Binary::apt::APT::Color", true); + _config->CndSet("Binary::apt::APT::Cache::Show::Version", 2); + _config->CndSet("Binary::apt::APT::Cache::AllVersions", false); + _config->CndSet("Binary::apt::APT::Cache::ShowVirtuals", true); + _config->CndSet("Binary::apt::APT::Cache::Search::Version", 2); + _config->CndSet("Binary::apt::APT::Cache::ShowDependencyType", true); + _config->CndSet("Binary::apt::APT::Cache::ShowVersion", true); + _config->CndSet("Binary::apt::APT::Get::Upgrade-Allow-New", true); + _config->CndSet("Binary::apt::APT::Cmd::Show-Update-Stats", true); + _config->CndSet("Binary::apt::DPkg::Progress-Fancy", true); + _config->CndSet("Binary::apt::APT::Keep-Downloaded-Packages", false); + _config->CndSet("Binary::apt::APT::Get::Update::InteractiveReleaseInfoChanges", true); + _config->CndSet("Binary::apt::APT::Cmd::Pattern-Only", true); + + if (isatty(STDIN_FILENO)) + _config->CndSet("Binary::apt::Dpkg::Lock::Timeout", -1); + else + _config->CndSet("Binary::apt::Dpkg::Lock::Timeout", 120); + } + + _config->Set("Binary", binary); +} + /*}}}*/ +static void BinaryCommandSpecificConfiguration(char const * const Binary, char const * const Cmd)/*{{{*/ +{ + std::string const binary = flNotDir(Binary); + if ((binary == "apt" || binary == "apt-get") && CmdMatches("upgrade", "dist-upgrade", "full-upgrade")) + { + //FIXME: the option is documented to apply only for install/remove, so + // we force it false for configuration files where users can be confused if + // we support it anyhow, but allow it on the commandline to take effect + // even through it isn't documented as a user who doesn't want it wouldn't + // ask for it + _config->Set("APT::Get::AutomaticRemove", ""); + } +} +#undef CmdMatches + /*}}}*/ +std::vector<CommandLine::Dispatch> ParseCommandLine(CommandLine &CmdL, APT_CMD const Binary,/*{{{*/ + Configuration * const * const Cnf, pkgSystem ** const Sys, int const argc, const char *argv[], + bool (*ShowHelp)(CommandLine &), std::vector<aptDispatchWithHelp> (*GetCommands)(void)) +{ + InitLocale(Binary); + if (Cnf != NULL && pkgInitConfig(**Cnf) == false) + { + _error->DumpErrors(); + exit(100); + } + + if (likely(argc != 0 && argv[0] != NULL)) + BinarySpecificConfiguration(argv[0]); + + std::vector<CommandLine::Dispatch> Cmds; + std::vector<aptDispatchWithHelp> const CmdsWithHelp = GetCommands(); + if (CmdsWithHelp.empty() == false) + { + CommandLine::Dispatch const help = { "help", [](CommandLine &){return false;} }; + Cmds.push_back(std::move(help)); + } + std::transform(CmdsWithHelp.begin(), CmdsWithHelp.end(), std::back_inserter(Cmds), + [](auto &&cmd) { return CommandLine::Dispatch{cmd.Match, cmd.Handler}; }); + + char const * CmdCalled = nullptr; + if (Cmds.empty() == false && Cmds[0].Handler != nullptr) + CmdCalled = CommandLine::GetCommand(Cmds.data(), argc, argv); + if (CmdCalled != nullptr) + BinaryCommandSpecificConfiguration(argv[0], CmdCalled); + std::string const conf = "Binary::" + _config->Find("Binary"); + _config->MoveSubTree(conf.c_str(), nullptr); + + // Args running out of scope invalidates the pointer stored in CmdL, + // but we don't use the pointer after this function, so we ignore + // this problem for now and figure something out if we have to. + auto Args = getCommandArgs(Binary, CmdCalled); + CmdL = CommandLine(Args.data(), _config); + + if (CmdL.Parse(argc,argv) == false || + (Sys != NULL && pkgInitSystem(*_config, *Sys) == false)) + { + if (_config->FindB("version") == true) + ShowCommonHelp(Binary, CmdL, CmdsWithHelp, ShowHelp); + + _error->DumpErrors(); + exit(100); + } + + if (_config->FindB("APT::Get::Force-Yes", false) == true) + { + _error->Warning(_("--force-yes is deprecated, use one of the options starting with --allow instead.")); + } + + // See if the help should be shown + if (_config->FindB("help") == true || _config->FindB("version") == true || + (CmdL.FileSize() > 0 && strcmp(CmdL.FileList[0], "help") == 0)) + { + ShowCommonHelp(Binary, CmdL, CmdsWithHelp, ShowHelp); + exit(0); + } + if (Cmds.empty() == false && CmdL.FileSize() == 0) + { + ShowCommonHelp(Binary, CmdL, CmdsWithHelp, ShowHelp); + exit(1); + } + return Cmds; +} + /*}}}*/ +unsigned short DispatchCommandLine(CommandLine &CmdL, std::vector<CommandLine::Dispatch> const &Cmds) /*{{{*/ +{ + // Match the operation + bool const returned = Cmds.empty() ? true : CmdL.DispatchArg(Cmds.data()); + + // Print any errors or warnings found during parsing + bool const Errors = _error->PendingError(); + if (_config->FindI("quiet",0) > 0) + _error->DumpErrors(); + else + _error->DumpErrors(GlobalError::DEBUG); + if (returned == false) + return 100; + return Errors == true ? 100 : 0; +} + /*}}}*/ diff --git a/apt-private/private-cmndline.h b/apt-private/private-cmndline.h new file mode 100644 index 0000000..22e25d2 --- /dev/null +++ b/apt-private/private-cmndline.h @@ -0,0 +1,42 @@ +#ifndef APT_PRIVATE_CMNDLINE_H +#define APT_PRIVATE_CMNDLINE_H + +#include <apt-pkg/cmndline.h> +#include <apt-pkg/macros.h> + +#include <vector> + +class Configuration; +class pkgSystem; + +enum class APT_CMD { + APT, + APT_GET, + APT_CACHE, + APT_CDROM, + APT_CONFIG, + APT_EXTRACTTEMPLATES, + APT_FTPARCHIVE, + APT_HELPER, + APT_INTERNAL_SOLVER, + APT_MARK, + APT_SORTPKG, + APT_DUMP_SOLVER, + APT_INTERNAL_PLANNER, + RRED, +}; +struct aptDispatchWithHelp +{ + const char *Match; + bool (*Handler)(CommandLine &); + const char *Help; +}; + +APT_PUBLIC std::vector<CommandLine::Dispatch> ParseCommandLine(CommandLine &CmdL, APT_CMD const Binary, + Configuration * const * const Cnf, pkgSystem ** const Sys, int const argc, const char * argv[], + bool (*ShowHelp)(CommandLine &), std::vector<aptDispatchWithHelp> (*GetCommands)(void)); +APT_PUBLIC unsigned short DispatchCommandLine(CommandLine &CmdL, std::vector<CommandLine::Dispatch> const &Cmds); + +APT_PUBLIC std::vector<CommandLine::Args> getCommandArgs(APT_CMD const Program, char const * const Cmd); + +#endif diff --git a/apt-private/private-depends.cc b/apt-private/private-depends.cc new file mode 100644 index 0000000..95c747e --- /dev/null +++ b/apt-private/private-depends.cc @@ -0,0 +1,151 @@ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/algorithms.h> +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/pkgcache.h> + +#include <apt-private/private-cacheset.h> +#include <apt-private/private-depends.h> + +#include <iostream> +#include <string> +#include <vector> + +#include <stddef.h> + +#include <apti18n.h> + /*}}}*/ + +// ShowDepends - Helper for printing out a dependency tree /*{{{*/ +static bool ShowDepends(CommandLine &CmdL, bool const RevDepends) +{ + pkgCacheFile CacheFile; + pkgCache * const Cache = CacheFile.GetPkgCache(); + if (unlikely(Cache == nullptr || CacheFile.GetDepCache() == nullptr)) + return false; + + CacheSetHelperVirtuals helper(false); + APT::VersionList verset = APT::VersionList::FromCommandLine(CacheFile, CmdL.FileList + 1, APT::CacheSetHelper::CANDIDATE, helper); + if (verset.empty() == true && helper.virtualPkgs.empty() == true) + return _error->Error(_("No packages found")); + std::vector<bool> Shown(Cache->Head().PackageCount); + + bool const Recurse = _config->FindB("APT::Cache::RecurseDepends", false); + bool const Installed = _config->FindB("APT::Cache::Installed", false); + bool const Important = _config->FindB("APT::Cache::Important", false); + bool const ShowDepType = _config->FindB("APT::Cache::ShowDependencyType", RevDepends == false); + bool const ShowVersion = _config->FindB("APT::Cache::ShowVersion", false); + bool const ShowPreDepends = _config->FindB("APT::Cache::ShowPre-Depends", true); + bool const ShowDepends = _config->FindB("APT::Cache::ShowDepends", true); + bool const ShowRecommends = _config->FindB("APT::Cache::ShowRecommends", Important == false); + bool const ShowSuggests = _config->FindB("APT::Cache::ShowSuggests", Important == false); + bool const ShowReplaces = _config->FindB("APT::Cache::ShowReplaces", Important == false); + bool const ShowConflicts = _config->FindB("APT::Cache::ShowConflicts", Important == false); + bool const ShowBreaks = _config->FindB("APT::Cache::ShowBreaks", Important == false); + bool const ShowEnhances = _config->FindB("APT::Cache::ShowEnhances", Important == false); + bool const ShowOnlyFirstOr = _config->FindB("APT::Cache::ShowOnlyFirstOr", false); + bool const ShowImplicit = _config->FindB("APT::Cache::ShowImplicit", false); + + while (verset.empty() != true) + { + pkgCache::VerIterator Ver = *verset.begin(); + verset.erase(verset.begin()); + pkgCache::PkgIterator Pkg = Ver.ParentPkg(); + Shown[Pkg->ID] = true; + + std::cout << Pkg.FullName(true) << std::endl; + + if (RevDepends == true) + std::cout << "Reverse Depends:" << std::endl; + for (pkgCache::DepIterator D = RevDepends ? Pkg.RevDependsList() : Ver.DependsList(); + D.end() == false; ++D) + { + switch (D->Type) { + case pkgCache::Dep::PreDepends: if (!ShowPreDepends) continue; break; + case pkgCache::Dep::Depends: if (!ShowDepends) continue; break; + case pkgCache::Dep::Recommends: if (!ShowRecommends) continue; break; + case pkgCache::Dep::Suggests: if (!ShowSuggests) continue; break; + case pkgCache::Dep::Replaces: if (!ShowReplaces) continue; break; + case pkgCache::Dep::Conflicts: if (!ShowConflicts) continue; break; + case pkgCache::Dep::DpkgBreaks: if (!ShowBreaks) continue; break; + case pkgCache::Dep::Enhances: if (!ShowEnhances) continue; break; + } + if (ShowImplicit == false && D.IsImplicit()) + continue; + + pkgCache::PkgIterator Trg = RevDepends ? D.ParentPkg() : D.TargetPkg(); + + if((Installed && Trg->CurrentVer != 0) || !Installed) + { + + if ((D->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or && ShowOnlyFirstOr == false) + std::cout << " |"; + else + std::cout << " "; + + // Show the package + if (ShowDepType == true) + std::cout << D.DepType() << ": "; + if (Trg->VersionList == 0) + std::cout << "<" << Trg.FullName(true) << ">"; + else + std::cout << Trg.FullName(true); + if (ShowVersion == true && D->Version != 0) + std::cout << " (" << pkgCache::CompTypeDeb(D->CompareOp) << ' ' << D.TargetVer() << ')'; + std::cout << std::endl; + + if (Recurse == true && Shown[Trg->ID] == false) + { + Shown[Trg->ID] = true; + verset.insert(APT::VersionSet::FromPackage(CacheFile, Trg, APT::CacheSetHelper::CANDIDATE, helper)); + } + + // Display all solutions + std::unique_ptr<pkgCache::Version *[]> List(D.AllTargets()); + pkgPrioSortList(*Cache,List.get()); + for (pkgCache::Version **I = List.get(); *I != 0; I++) + { + pkgCache::VerIterator V(*Cache,*I); + if (V != Cache->VerP + V.ParentPkg()->VersionList || + V->ParentPkg == D->Package) + continue; + std::cout << " " << V.ParentPkg().FullName(true) << std::endl; + + if (Recurse == true && Shown[V.ParentPkg()->ID] == false) + { + Shown[V.ParentPkg()->ID] = true; + verset.insert(APT::VersionSet::FromPackage(CacheFile, V.ParentPkg(), APT::CacheSetHelper::CANDIDATE, helper)); + } + } + + } + + if (ShowOnlyFirstOr == true) + while ((D->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or) ++D; + } + } + + for (APT::PackageSet::const_iterator Pkg = helper.virtualPkgs.begin(); + Pkg != helper.virtualPkgs.end(); ++Pkg) + std::cout << '<' << Pkg.FullName(true) << '>' << std::endl; + + return true; +} + /*}}}*/ +// Depends - Print out a dependency tree /*{{{*/ +bool Depends(CommandLine &CmdL) +{ + return ShowDepends(CmdL, false); +} + /*}}}*/ +// RDepends - Print out a reverse dependency tree /*{{{*/ +bool RDepends(CommandLine &CmdL) +{ + return ShowDepends(CmdL, true); +} + /*}}}*/ diff --git a/apt-private/private-depends.h b/apt-private/private-depends.h new file mode 100644 index 0000000..e9f703d --- /dev/null +++ b/apt-private/private-depends.h @@ -0,0 +1,11 @@ +#ifndef APT_PRIVATE_DEPENDS_H +#define APT_PRIVATE_DEPENDS_H + +#include <apt-pkg/macros.h> + +class CommandLine; + +APT_PUBLIC bool Depends(CommandLine &CmdL); +APT_PUBLIC bool RDepends(CommandLine &CmdL); + +#endif diff --git a/apt-private/private-download.cc b/apt-private/private-download.cc new file mode 100644 index 0000000..eddb901 --- /dev/null +++ b/apt-private/private-download.cc @@ -0,0 +1,382 @@ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/acquire-item.h> +#include <apt-pkg/acquire.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/clean.h> +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/strutl.h> + +#include <apt-private/acqprogress.h> +#include <apt-private/private-cachefile.h> +#include <apt-private/private-download.h> +#include <apt-private/private-output.h> +#include <apt-private/private-utils.h> + +#include <fstream> +#include <string> +#include <vector> + +#include <fcntl.h> +#include <pwd.h> +#include <sys/types.h> +#include <unistd.h> +#ifdef HAVE_VFS_H +#include <sys/vfs.h> +#else +#ifdef HAVE_PARAMS_H +#include <sys/params.h> +#endif +#include <sys/mount.h> +#endif +#include <errno.h> +#include <sys/stat.h> +#include <sys/statvfs.h> + +#include <apti18n.h> + /*}}}*/ + +// CheckAuth - check if each download comes form a trusted source /*{{{*/ +bool CheckAuth(pkgAcquire& Fetcher, bool const PromptUser) +{ + std::vector<std::string> UntrustedList; + for (pkgAcquire::ItemIterator I = Fetcher.ItemsBegin(); I < Fetcher.ItemsEnd(); ++I) + if (!(*I)->IsTrusted()) + UntrustedList.push_back((*I)->ShortDesc()); + + if (UntrustedList.empty()) + return true; + + return AuthPrompt(UntrustedList, PromptUser); +} + /*}}}*/ +bool AuthPrompt(std::vector<std::string> const &UntrustedList, bool const PromptUser)/*{{{*/ +{ + ShowList(c2out,_("WARNING: The following packages cannot be authenticated!"), UntrustedList, + [](std::string const&) { return true; }, + [](std::string const&str) { return str; }, + [](std::string const&) { return ""; }); + + if (_config->FindB("APT::Get::AllowUnauthenticated",false) == true) + { + c2out << _("Authentication warning overridden.\n"); + return true; + } + + if (PromptUser == false) + return _error->Error(_("Some packages could not be authenticated")); + + if (_config->FindI("quiet",0) < 2 + && _config->FindB("APT::Get::Assume-Yes",false) == false) + { + if (!YnPrompt(_("Install these packages without verification?"), false)) + return _error->Error(_("Some packages could not be authenticated")); + + return true; + } + else if (_config->FindB("APT::Get::Force-Yes",false) == true) { + return true; + } + + return _error->Error(_("There were unauthenticated packages and -y was used without --allow-unauthenticated")); +} + /*}}}*/ +bool AcquireRun(pkgAcquire &Fetcher, int const PulseInterval, bool * const Failure, bool * const TransientNetworkFailure)/*{{{*/ +{ + pkgAcquire::RunResult res; + if(PulseInterval > 0) + res = Fetcher.Run(PulseInterval); + else + res = Fetcher.Run(); + + if (res == pkgAcquire::Failed) + return false; + + for (pkgAcquire::ItemIterator I = Fetcher.ItemsBegin(); + I != Fetcher.ItemsEnd(); ++I) + { + + if ((*I)->Status == pkgAcquire::Item::StatDone && + (*I)->Complete == true) + continue; + + if (TransientNetworkFailure != NULL && (*I)->Status == pkgAcquire::Item::StatIdle) + { + *TransientNetworkFailure = true; + continue; + } + + ::URI uri((*I)->DescURI()); + uri.User.clear(); + uri.Password.clear(); + std::string descUri = std::string(uri); + _error->Error(_("Failed to fetch %s %s"), descUri.c_str(), + (*I)->ErrorText.c_str()); + + if (Failure != NULL) + *Failure = true; + } + + return true; +} + /*}}}*/ +bool CheckFreeSpaceBeforeDownload(std::string const &Dir, unsigned long long FetchBytes)/*{{{*/ +{ + uint32_t const RAMFS_MAGIC = 0x858458f6; + /* Check for enough free space, but only if we are actually going to + download */ + if (_config->FindB("APT::Get::Print-URIs", false) == true || + _config->FindB("APT::Get::Download", true) == false) + return true; + + struct statvfs Buf; + if (statvfs(Dir.c_str(),&Buf) != 0) { + if (errno == EOVERFLOW) + return _error->WarningE("statvfs",_("Couldn't determine free space in %s"), + Dir.c_str()); + else + return _error->Errno("statvfs",_("Couldn't determine free space in %s"), + Dir.c_str()); + } + else + { + unsigned long long const FreeBlocks = _config->Find("APT::Sandbox::User").empty() ? Buf.f_bfree : Buf.f_bavail; + if (FreeBlocks < (FetchBytes / Buf.f_bsize)) + { + struct statfs Stat; + if (statfs(Dir.c_str(),&Stat) != 0 +#ifdef HAVE_STRUCT_STATFS_F_TYPE + || Stat.f_type != RAMFS_MAGIC +#endif + ) + return _error->Error(_("You don't have enough free space in %s."), + Dir.c_str()); + } + } + return true; +} + /*}}}*/ + +aptAcquireWithTextStatus::aptAcquireWithTextStatus() : pkgAcquire::pkgAcquire(), + Stat(std::cout, ScreenWidth, _config->FindI("quiet",0)) +{ + SetLog(&Stat); +} + +// DoDownload - download a binary /*{{{*/ +bool DoDownload(CommandLine &CmdL) +{ + CacheFile Cache; + if (Cache.ReadOnlyOpen() == false) + return false; + + APT::CacheSetHelper helper; + APT::VersionSet verset = APT::VersionSet::FromCommandLine(Cache, + CmdL.FileList + 1, APT::CacheSetHelper::CANDIDATE, helper); + + if (verset.empty() == true) + return false; + + pkgRecords Recs(Cache); + pkgSourceList *SrcList = Cache.GetSourceList(); + + // reuse the usual acquire methods for deb files, but don't drop them into + // the usual directories - keep everything in the current directory + aptAcquireWithTextStatus Fetcher; + std::vector<std::string> storefile(verset.size()); + std::string const cwd = SafeGetCWD(); + _config->Set("Dir::Cache::Archives", cwd); + int i = 0; + for (APT::VersionSet::const_iterator Ver = verset.begin(); + Ver != verset.end(); ++Ver, ++i) + { + pkgAcquire::Item *I = new pkgAcqArchive(&Fetcher, SrcList, &Recs, *Ver, storefile[i]); + if (storefile[i].empty()) + continue; + std::string const filename = cwd + flNotDir(storefile[i]); + storefile[i].assign(filename); + I->DestFile.assign(filename); + } + + // Just print out the uris and exit if the --print-uris flag was used + if (_config->FindB("APT::Get::Print-URIs") == true) + { + pkgAcquire::UriIterator I = Fetcher.UriBegin(); + for (; I != Fetcher.UriEnd(); ++I) + std::cout << '\'' << I->URI << "' " << flNotDir(I->Owner->DestFile) << ' ' << + I->Owner->FileSize << ' ' << I->Owner->HashSum() << std::endl; + return true; + } + auto const storecopy = storefile; + + if (_error->PendingError() == true || CheckAuth(Fetcher, false) == false) + return false; + + bool Failed = false; + if (AcquireRun(Fetcher, 0, &Failed, NULL) == false) + return false; + + // copy files in local sources to the current directory + i = 0; + for (pkgAcquire::ItemIterator I = Fetcher.ItemsBegin(); I != Fetcher.ItemsEnd(); ++I) + { + if (dynamic_cast<pkgAcqArchive*>(*I) == nullptr) + continue; + + if ((*I)->Local == true && + (*I)->Status == pkgAcquire::Item::StatDone && + (*I)->DestFile != storecopy[i]) + { + std::ifstream src((*I)->DestFile.c_str(), std::ios::binary); + std::ofstream dst(storecopy[i].c_str(), std::ios::binary); + dst << src.rdbuf(); + chmod(storecopy[i].c_str(), 0644); + } + ++i; + } + return Failed == false; +} + /*}}}*/ +// DoChangelog - Get changelog from the command line /*{{{*/ +bool DoChangelog(CommandLine &CmdL) +{ + CacheFile Cache; + if (Cache.ReadOnlyOpen() == false) + return false; + + APT::CacheSetHelper helper; + APT::VersionList verset = APT::VersionList::FromCommandLine(Cache, + CmdL.FileList + 1, APT::CacheSetHelper::CANDIDATE, helper); + if (verset.empty() == true) + return _error->Error(_("No packages found")); + + bool const downOnly = _config->FindB("APT::Get::Download-Only", false); + bool const printOnly = _config->FindB("APT::Get::Print-URIs", false); + if (printOnly) + _config->CndSet("Acquire::Changelogs::AlwaysOnline", true); + + aptAcquireWithTextStatus Fetcher; + for (APT::VersionList::const_iterator Ver = verset.begin(); + Ver != verset.end(); + ++Ver) + { + if (printOnly) + new pkgAcqChangelog(&Fetcher, Ver, "/dev/null"); + else if (downOnly) + new pkgAcqChangelog(&Fetcher, Ver, "."); + else + new pkgAcqChangelog(&Fetcher, Ver); + } + + if (printOnly == false) + { + bool Failed = false; + if (AcquireRun(Fetcher, 0, &Failed, NULL) == false || Failed == true) + return false; + } + + if (downOnly == false || printOnly == true) + { + bool Failed = false; + for (pkgAcquire::ItemIterator I = Fetcher.ItemsBegin(); I != Fetcher.ItemsEnd(); ++I) + { + if (printOnly) + { + if ((*I)->ErrorText.empty() == false) + { + Failed = true; + _error->Error("%s", (*I)->ErrorText.c_str()); + } + else + std::cout << '\'' << (*I)->DescURI() << "' " << flNotDir((*I)->DestFile) << std::endl; + } + else + DisplayFileInPager((*I)->DestFile); + } + return Failed == false; + } + + return true; +} + /*}}}*/ + +// DoClean - Remove download archives /*{{{*/ +bool DoClean(CommandLine &) +{ + std::string const archivedir = _config->FindDir("Dir::Cache::archives"); + std::string const listsdir = _config->FindDir("Dir::state::lists"); + + if (_config->FindB("APT::Get::Simulate") == true) + { + std::string const pkgcache = _config->FindFile("Dir::cache::pkgcache"); + std::string const srcpkgcache = _config->FindFile("Dir::cache::srcpkgcache"); + std::cout << "Del " << archivedir << "* " << archivedir << "partial/*"<< std::endl + << "Del " << listsdir << "partial/*" << std::endl + << "Del " << pkgcache << " " << srcpkgcache << std::endl; + return true; + } + + pkgAcquire Fetcher; + if (archivedir.empty() == false && FileExists(archivedir) == true && + Fetcher.GetLock(archivedir) == true) + { + Fetcher.Clean(archivedir); + Fetcher.Clean(archivedir + "partial/"); + } + + if (listsdir.empty() == false && FileExists(listsdir) == true && + Fetcher.GetLock(listsdir) == true) + { + Fetcher.Clean(listsdir + "partial/"); + } + + pkgCacheFile::RemoveCaches(); + + return true; +} + /*}}}*/ +// DoAutoClean - Smartly remove downloaded archives /*{{{*/ +// --------------------------------------------------------------------- +/* This is similar to clean but it only purges things that cannot be + downloaded, that is old versions of cached packages. */ + class LogCleaner : public pkgArchiveCleaner +{ + protected: + virtual void Erase(int const dirfd, char const * const File, std::string const &Pkg, std::string const &Ver,struct stat const &St) APT_OVERRIDE + { + c1out << "Del " << Pkg << " " << Ver << " [" << SizeToStr(St.st_size) << "B]" << std::endl; + + if (_config->FindB("APT::Get::Simulate") == false) + RemoveFileAt("Cleaner::Erase", dirfd, File); + }; +}; +bool DoAutoClean(CommandLine &) +{ + std::string const archivedir = _config->FindDir("Dir::Cache::Archives"); + if (FileExists(archivedir) == false) + return true; + + // Lock the archive directory + FileFd Lock; + if (_config->FindB("Debug::NoLocking",false) == false) + { + int lock_fd = GetLock(flCombine(archivedir, "lock")); + if (lock_fd < 0) + return _error->Error(_("Unable to lock the download directory")); + Lock.Fd(lock_fd); + } + + CacheFile Cache; + if (Cache.Open(false) == false) + return false; + + LogCleaner Cleaner; + + return Cleaner.Go(archivedir, *Cache) && + Cleaner.Go(flCombine(archivedir, "partial/"), *Cache); +} + /*}}}*/ diff --git a/apt-private/private-download.h b/apt-private/private-download.h new file mode 100644 index 0000000..d829e8b --- /dev/null +++ b/apt-private/private-download.h @@ -0,0 +1,38 @@ +#ifndef APT_PRIVATE_DOWNLOAD_H +#define APT_PRIVATE_DOWNLOAD_H + +#include <apt-pkg/acquire.h> +#include <apt-pkg/macros.h> + +#include <apt-private/acqprogress.h> + +#include <string> +#include <vector> + +// Check if all files in the fetcher are authenticated +bool CheckAuth(pkgAcquire& Fetcher, bool const PromptUser); + +// show a authentication warning prompt and return true if the system +// should continue +bool AuthPrompt(std::vector<std::string> const &UntrustedList, bool const PromptUser); + +APT_PUBLIC bool AcquireRun(pkgAcquire &Fetcher, int const PulseInterval, bool * const Failure, bool * const TransientNetworkFailure); + +bool CheckFreeSpaceBeforeDownload(std::string const &Dir, unsigned long long FetchBytes); + +class APT_PUBLIC aptAcquireWithTextStatus : public pkgAcquire +{ + AcqTextStatus Stat; +public: + aptAcquireWithTextStatus(); +}; + +class CommandLine; + +APT_PUBLIC bool DoDownload(CommandLine &CmdL); +APT_PUBLIC bool DoChangelog(CommandLine &CmdL); + +APT_PUBLIC bool DoClean(CommandLine &CmdL); +APT_PUBLIC bool DoAutoClean(CommandLine &CmdL); + +#endif diff --git a/apt-private/private-install.cc b/apt-private/private-install.cc new file mode 100644 index 0000000..08d58a5 --- /dev/null +++ b/apt-private/private-install.cc @@ -0,0 +1,1147 @@ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/acquire-item.h> +#include <apt-pkg/acquire.h> +#include <apt-pkg/algorithms.h> +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/install-progress.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/packagemanager.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/pkgsystem.h> +#include <apt-pkg/prettyprinters.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/upgrade.h> + +#include <algorithm> +#include <iostream> +#include <map> +#include <set> +#include <vector> +#include <stdlib.h> +#include <string.h> + +#include <apt-private/acqprogress.h> +#include <apt-private/private-cachefile.h> +#include <apt-private/private-cacheset.h> +#include <apt-private/private-download.h> +#include <apt-private/private-install.h> +#include <apt-private/private-json-hooks.h> +#include <apt-private/private-output.h> + +#include <apti18n.h> + /*}}}*/ +class pkgSourceList; + +bool CheckNothingBroken(CacheFile &Cache) /*{{{*/ +{ + // Now we check the state of the packages, + if (Cache->BrokenCount() == 0) + return true; + + // FIXME: if an external solver showed an error, we shouldn't show one here + if (_error->PendingError() && _config->Find("APT::Solver") == "dump") + return false; + + c1out << + _("Some packages could not be installed. This may mean that you have\n" + "requested an impossible situation or if you are using the unstable\n" + "distribution that some required packages have not yet been created\n" + "or been moved out of Incoming.") << std::endl; + /* + if (Packages == 1) + { + c1out << std::endl; + c1out << + _("Since you only requested a single operation it is extremely likely that\n" + "the package is simply not installable and a bug report against\n" + "that package should be filed.") << std::endl; + } + */ + + c1out << _("The following information may help to resolve the situation:") << std::endl; + c1out << std::endl; + ShowBroken(c1out,Cache,false); + if (_error->PendingError() == true) + return false; + else + return _error->Error(_("Broken packages")); +} + /*}}}*/ +// InstallPackages - Actually download and install the packages /*{{{*/ +// --------------------------------------------------------------------- +/* This displays the informative messages describing what is going to + happen and then calls the download routines */ +class SimulateWithActionGroupInhibited : public pkgSimulate +{ +public: + SimulateWithActionGroupInhibited(CacheFile &Cache) : pkgSimulate(Cache) { Sim.IncreaseActionGroupLevel(); } + SimulateWithActionGroupInhibited(SimulateWithActionGroupInhibited const &Cache) = delete; + SimulateWithActionGroupInhibited(SimulateWithActionGroupInhibited &&Cache) = delete; + SimulateWithActionGroupInhibited& operator=(SimulateWithActionGroupInhibited const &Cache) = delete; + SimulateWithActionGroupInhibited& operator=(SimulateWithActionGroupInhibited &&Cache) = delete; + ~SimulateWithActionGroupInhibited() = default; +}; +static void RemoveDownloadNeedingItemsFromFetcher(pkgAcquire &Fetcher, bool &Transient) +{ + for (pkgAcquire::ItemIterator I = Fetcher.ItemsBegin(); I < Fetcher.ItemsEnd();) + { + if ((*I)->Local == true) + { + ++I; + continue; + } + + // Close the item and check if it was found in cache + (*I)->Finished(); + if ((*I)->Complete == false) + Transient = true; + + // Clear it out of the fetch list + delete *I; + I = Fetcher.ItemsBegin(); + } +} +bool InstallPackages(CacheFile &Cache, APT::PackageVector &HeldBackPackages, bool ShwKept, bool Ask, bool Safety, std::string const &Hook, CommandLine const &CmdL) +{ + if (not RunScripts("APT::Install::Pre-Invoke")) + return false; + if (_config->FindB("APT::Get::Purge", false) == true) + for (pkgCache::PkgIterator I = Cache->PkgBegin(); I.end() == false; ++I) + if (Cache[I].Delete() == true && Cache[I].Purge() == false) + Cache->MarkDelete(I,true); + + // Create the download object + auto const DownloadAllowed = _config->FindB("APT::Get::Download",true); + aptAcquireWithTextStatus Fetcher; + if (_config->FindB("APT::Get::Print-URIs", false) == true) + { + // force a hashsum for compatibility reasons + _config->CndSet("Acquire::ForceHash", "md5sum"); + } + else if (_config->FindB("APT::Get::Simulate") == true) + ; + else if (Fetcher.GetLock(_config->FindDir("Dir::Cache::Archives")) == false) + return false; + + // Read the source list + if (Cache.BuildSourceList() == false) + return false; + pkgSourceList * const List = Cache.GetSourceList(); + + // Create the text record parser + pkgRecords Recs(Cache); + if (_error->PendingError() == true) + return false; + + // Create the package manager and prepare to download + std::unique_ptr<pkgPackageManager> PM(_system->CreatePM(Cache)); + if (PM->GetArchives(&Fetcher,List,&Recs) == false || + _error->PendingError() == true) + return false; + + if (DownloadAllowed == false) + { + bool Missing = false; + RemoveDownloadNeedingItemsFromFetcher(Fetcher, Missing); + if (Missing) + { + if (_config->FindB("APT::Get::Fix-Missing",false)) + { + PM->FixMissing(); + SortedPackageUniverse Universe(Cache); + APT::PackageVector NewHeldBackPackages; + for (auto const &Pkg: Universe) + { + if (Pkg->CurrentVer == 0 || Cache[Pkg].Delete()) + continue; + if (Cache[Pkg].Upgradable() && not Cache[Pkg].Upgrade()) + NewHeldBackPackages.push_back(Pkg); + else if (std::find(HeldBackPackages.begin(), HeldBackPackages.end(), Pkg) != HeldBackPackages.end()) + NewHeldBackPackages.push_back(Pkg); + } + std::swap(NewHeldBackPackages, HeldBackPackages); + } + else + return _error->Error(_("Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?")); + } + Fetcher.Shutdown(); + if (_error->PendingError() == true) + return false; + } + + // Show all the various warning indicators + ShowDel(c1out,Cache); + ShowNew(c1out,Cache); + if (ShwKept == true) + ShowKept(c1out,Cache, HeldBackPackages); + bool const Hold = not ShowHold(c1out,Cache); + if (_config->FindB("APT::Get::Show-Upgraded",true) == true) + ShowUpgraded(c1out,Cache); + bool const Downgrade = !ShowDowngraded(c1out,Cache); + + bool Essential = false; + if (_config->FindB("APT::Get::Download-Only",false) == false) + Essential = !ShowEssential(c1out,Cache); + + if (not Hook.empty()) + RunJsonHook(Hook, "org.debian.apt.hooks.install.package-list", CmdL.FileList, Cache); + + Stats(c1out,Cache, HeldBackPackages); + if (not Hook.empty()) + RunJsonHook(Hook, "org.debian.apt.hooks.install.statistics", CmdL.FileList, Cache); + + // Sanity check + if (Cache->BrokenCount() != 0) + { + ShowBroken(c1out,Cache,false); + return _error->Error(_("Internal error, InstallPackages was called with broken packages!")); + } + + if (Cache->DelCount() == 0 && Cache->InstCount() == 0 && + Cache->BadCount() == 0) + return RunScripts("APT::Install::Post-Invoke-Success"); + + // No remove flag + if (Cache->DelCount() != 0 && _config->FindB("APT::Get::Remove",true) == false) + return _error->Error(_("Packages need to be removed but remove is disabled.")); + + // Fail safe check + bool const Fail = (Essential || Downgrade || Hold); + if (_config->FindI("quiet",0) >= 2 || + _config->FindB("APT::Get::Assume-Yes",false) == true) + { + if (Fail == true && _config->FindB("APT::Get::Force-Yes",false) == false) { + if (Essential == true && _config->FindB("APT::Get::allow-remove-essential", false) == false) + return _error->Error(_("Essential packages were removed and -y was used without --allow-remove-essential.")); + if (Downgrade == true && _config->FindB("APT::Get::allow-downgrades", false) == false) + return _error->Error(_("Packages were downgraded and -y was used without --allow-downgrades.")); + if (Hold == true && _config->FindB("APT::Get::allow-change-held-packages", false) == false) + return _error->Error(_("Held packages were changed and -y was used without --allow-change-held-packages.")); + } + } + + // Run the simulator .. + if (_config->FindB("APT::Get::Simulate") == true) + { + SimulateWithActionGroupInhibited PM(Cache); + + APT::Progress::PackageManager *progress = APT::Progress::PackageManagerProgressFactory(); + pkgPackageManager::OrderResult Res = PM.DoInstall(progress); + delete progress; + + if (Res == pkgPackageManager::Failed) + return false; + if (Res != pkgPackageManager::Completed) + return _error->Error(_("Internal error, Ordering didn't finish")); + return true; + } + + auto const FetchBytes = DownloadAllowed ? Fetcher.FetchNeeded() : 0; + auto const FetchPBytes = DownloadAllowed ? Fetcher.PartialPresent() : 0; + if (DownloadAllowed) + { + // Display statistics + auto const DebBytes = Fetcher.TotalNeeded(); + if (DebBytes != Cache->DebSize()) + { + c0out << "E: " << DebBytes << ',' << Cache->DebSize() << std::endl; + c0out << "E: " << _("How odd... The sizes didn't match, email apt@packages.debian.org") << std::endl; + } + + // Number of bytes + if (DebBytes != FetchBytes) + //TRANSLATOR: The required space between number and unit is already included + // in the replacement strings, so %sB will be correctly translate in e.g. 1,5 MB + ioprintf(c1out,_("Need to get %sB/%sB of archives.\n"), + SizeToStr(FetchBytes).c_str(),SizeToStr(DebBytes).c_str()); + else if (DebBytes != 0) + //TRANSLATOR: The required space between number and unit is already included + // in the replacement string, so %sB will be correctly translate in e.g. 1,5 MB + ioprintf(c1out,_("Need to get %sB of archives.\n"), + SizeToStr(DebBytes).c_str()); + } + + // Size delta + if (Cache->UsrSize() >= 0) + //TRANSLATOR: The required space between number and unit is already included + // in the replacement string, so %sB will be correctly translate in e.g. 1,5 MB + ioprintf(c1out,_("After this operation, %sB of additional disk space will be used.\n"), + SizeToStr(Cache->UsrSize()).c_str()); + else + //TRANSLATOR: The required space between number and unit is already included + // in the replacement string, so %sB will be correctly translate in e.g. 1,5 MB + ioprintf(c1out,_("After this operation, %sB disk space will be freed.\n"), + SizeToStr(-1*Cache->UsrSize()).c_str()); + + if (DownloadAllowed) + if (CheckFreeSpaceBeforeDownload(_config->FindDir("Dir::Cache::Archives"), (FetchBytes - FetchPBytes)) == false) + return false; + + if (_error->PendingError() == true) + return false; + + // Just print out the uris an exit if the --print-uris flag was used + if (_config->FindB("APT::Get::Print-URIs") == true) + { + pkgAcquire::UriIterator I = Fetcher.UriBegin(); + for (; I != Fetcher.UriEnd(); ++I) + std::cout << '\'' << I->URI << "' " << flNotDir(I->Owner->DestFile) << ' ' << + std::to_string(I->Owner->FileSize) << ' ' << I->Owner->HashSum() << std::endl; + return true; + } + + if (Essential == true && Safety == true && _config->FindB("APT::Get::allow-remove-essential", false) == false) + { + if (_config->FindB("APT::Get::Trivial-Only",false) == true) + return _error->Error(_("Trivial Only specified but this is not a trivial operation.")); + + return _error->Error(_("Removing essential system-critical packages is not permitted. This might break the system.")); + } + else + { + // Prompt to continue + if (Ask == true || Fail == true) + { + if (_config->FindB("APT::Get::Trivial-Only",false) == true) + return _error->Error(_("Trivial Only specified but this is not a trivial operation.")); + + if (_config->FindI("quiet",0) < 2 && + _config->FindB("APT::Get::Assume-Yes",false) == false) + { + if (YnPrompt(_("Do you want to continue?")) == false) + { + c2out << _("Abort.") << std::endl; + exit(1); + } + } + } + } + + if (!CheckAuth(Fetcher, true)) + return false; + + /* Unlock the dpkg lock if we are not going to be doing an install + after. */ + if (_config->FindB("APT::Get::Download-Only",false) == true) + _system->UnLock(); + + // Run it + bool Failed = false; + while (1) + { + bool Transient = false; + if (AcquireRun(Fetcher, 0, &Failed, &Transient) == false) + return false; + + if (_config->FindB("APT::Get::Download-Only",false) == true) + { + if (Failed == true && _config->FindB("APT::Get::Fix-Missing",false) == false) + return _error->Error(_("Some files failed to download")); + c1out << _("Download complete and in download only mode") << std::endl; + return true; + } + + if (Failed == true && _config->FindB("APT::Get::Fix-Missing",false) == false) + return _error->Error(_("Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?")); + + if (Transient == true && Failed == true) + return _error->Error(_("--fix-missing and media swapping is not currently supported")); + + // Try to deal with missing package files + if (Failed == true && PM->FixMissing() == false) + { + c2out << _("Unable to correct missing packages.") << std::endl; + return _error->Error(_("Aborting install.")); + } + + auto const progress = APT::Progress::PackageManagerProgressFactory(); + _system->UnLockInner(); + pkgPackageManager::OrderResult const Res = PM->DoInstall(progress); + delete progress; + + if (Res == pkgPackageManager::Failed || _error->PendingError() == true) + return false; + if (Res == pkgPackageManager::Completed) + break; + + _system->LockInner(); + + // Reload the fetcher object and loop again for media swapping + Fetcher.Shutdown(); + if (PM->GetArchives(&Fetcher,List,&Recs) == false) + return false; + + Failed = false; + if (DownloadAllowed == false) + RemoveDownloadNeedingItemsFromFetcher(Fetcher, Failed); + } + + std::set<std::string> const disappearedPkgs = PM->GetDisappearedPackages(); + if (disappearedPkgs.empty() == false) + { + ShowList(c1out, P_("The following package disappeared from your system as\n" + "all files have been overwritten by other packages:", + "The following packages disappeared from your system as\n" + "all files have been overwritten by other packages:", disappearedPkgs.size()), disappearedPkgs, + [](std::string const &Pkg) { return Pkg.empty() == false; }, + [](std::string const &Pkg) { return Pkg; }, + [](std::string const &) { return std::string(); }); + c0out << _("Note: This is done automatically and on purpose by dpkg.") << std::endl; + } + + // cleanup downloaded debs + if (_config->FindB("APT::Keep-Downloaded-Packages", true) == false) + { + std::string const archivedir = _config->FindDir("Dir::Cache::archives"); + for (auto I = Fetcher.ItemsBegin(); I != Fetcher.ItemsEnd(); ++I) + { + if (flNotFile((*I)->DestFile) != archivedir || (*I)->Local) + continue; + RemoveFile("Keep-Downloaded-Packages=false", (*I)->DestFile); + } + } + + if (not RunScripts("APT::Install::Post-Invoke-Success")) + return false; + + return true; +} + /*}}}*/ +// DoAutomaticRemove - Remove all automatic unused packages /*{{{*/ +// --------------------------------------------------------------------- +/* Remove unused automatic packages */ +bool DoAutomaticRemove(CacheFile &Cache) +{ + bool Debug = _config->FindB("Debug::pkgAutoRemove",false); + bool doAutoRemove = _config->FindB("APT::Get::AutomaticRemove", false); + bool doAutoRemoveKernels = _config->FindB("APT::Get::AutomaticRemove::Kernels", false); + bool hideAutoRemove = _config->FindB("APT::Get::HideAutoRemove"); + + std::unique_ptr<APT::CacheFilter::Matcher> kernelAutoremovalMatcher; + if (doAutoRemoveKernels && !doAutoRemove) + { + kernelAutoremovalMatcher = APT::KernelAutoRemoveHelper::GetProtectedKernelsFilter(Cache, true); + } + + if(Debug) + std::cout << "DoAutomaticRemove()" << std::endl; + + if (doAutoRemove == true && + _config->FindB("APT::Get::Remove",true) == false) + { + c1out << _("We are not supposed to delete stuff, can't start " + "AutoRemover") << std::endl; + return false; + } + Cache->MarkAndSweep(); + + bool purgePkgs = _config->FindB("APT::Get::Purge", false); + bool smallList = (hideAutoRemove == false && + strcasecmp(_config->Find("APT::Get::HideAutoRemove","").c_str(),"small") == 0); + + unsigned long autoRemoveCount = 0; + APT::PackageSet tooMuch; + SortedPackageUniverse Universe(Cache); + // look over the cache to see what can be removed + for (auto const &Pkg: Universe) + { + if (Cache[Pkg].Garbage) + { + if(Pkg.CurrentVer() != 0 || Cache[Pkg].Install()) + if(Debug) + std::cout << "We could delete " << APT::PrettyPkg(Cache, Pkg) << std::endl; + + if (doAutoRemove || (kernelAutoremovalMatcher != nullptr && (*kernelAutoremovalMatcher)(Pkg))) + { + if(Pkg.CurrentVer() != 0 && + Pkg->CurrentState != pkgCache::State::ConfigFiles) + Cache->MarkDelete(Pkg, purgePkgs, 0, false); + else + Cache->MarkKeep(Pkg, false, false); + } + else + { + // if the package is a new install and already garbage we don't need to + // install it in the first place, so nuke it instead of show it + if (Cache[Pkg].Install() == true && Pkg.CurrentVer() == 0) + { + tooMuch.insert(Pkg); + Cache->MarkDelete(Pkg, false, 0, false); + } + // only show stuff in the list that is not yet marked for removal + else if(hideAutoRemove == false && Cache[Pkg].Delete() == false) + ++autoRemoveCount; + } + } + } + + // we could have removed a new dependency of a garbage package, + // so check if a reverse depends is broken and if so install it again. + if (tooMuch.empty() == false && (Cache->BrokenCount() != 0 || Cache->PolicyBrokenCount() != 0)) + { + bool Changed; + do { + Changed = false; + for (APT::PackageSet::iterator Pkg = tooMuch.begin(); + Pkg != tooMuch.end(); ++Pkg) + { + APT::PackageSet too; + too.insert(*Pkg); + for (pkgCache::PrvIterator Prv = Cache[Pkg].CandidateVerIter(Cache).ProvidesList(); + Prv.end() == false; ++Prv) + too.insert(Prv.ParentPkg()); + for (APT::PackageSet::const_iterator P = too.begin(); P != too.end(); ++P) + { + for (pkgCache::DepIterator R = P.RevDependsList(); + R.end() == false; ++R) + { + if (R.IsNegative() == true || + Cache->IsImportantDep(R) == false) + continue; + auto const RV = R.ParentVer(); + if (unlikely(RV.end() == true)) + continue; + auto const RP = RV.ParentPkg(); + // check if that dependency comes from an interesting version + if (RP.CurrentVer() == RV) + { + if ((*Cache)[RP].Keep() == false) + continue; + } + else if (Cache[RP].CandidateVerIter(Cache) == RV) + { + if ((*Cache)[RP].NewInstall() == false && (*Cache)[RP].Upgrade() == false) + continue; + } + else // ignore dependency from a non-candidate version + continue; + if (Debug == true) + std::clog << "Save " << APT::PrettyPkg(Cache, Pkg) << " as another installed package depends on it: " << APT::PrettyPkg(Cache, RP) << std::endl; + Cache->MarkInstall(Pkg, false, 0, false); + if (hideAutoRemove == false) + ++autoRemoveCount; + tooMuch.erase(Pkg); + Changed = true; + break; + } + if (Changed == true) + break; + } + if (Changed == true) + break; + } + } while (Changed == true); + } + + // Now see if we had destroyed anything (if we had done anything) + if (Cache->BrokenCount() != 0) + { + c1out << _("Hmm, seems like the AutoRemover destroyed something which really\n" + "shouldn't happen. Please file a bug report against apt.") << std::endl; + c1out << std::endl; + c1out << _("The following information may help to resolve the situation:") << std::endl; + c1out << std::endl; + ShowBroken(c1out,Cache,false); + + return _error->Error(_("Internal Error, AutoRemover broke stuff")); + } + + // if we don't remove them, we should show them! + if (doAutoRemove == false && autoRemoveCount != 0) + { + if (smallList == false) + { + // trigger marking now so that the package list is correct + Cache->MarkAndSweep(); + SortedPackageUniverse Universe(Cache); + ShowList(c1out, P_("The following package was automatically installed and is no longer required:", + "The following packages were automatically installed and are no longer required:", + autoRemoveCount), Universe, + [&Cache](pkgCache::PkgIterator const &Pkg) { return (*Cache)[Pkg].Garbage == true && (*Cache)[Pkg].Delete() == false; }, + &PrettyFullName, CandidateVersion(&Cache)); + } + else + ioprintf(c1out, P_("%lu package was automatically installed and is no longer required.\n", + "%lu packages were automatically installed and are no longer required.\n", autoRemoveCount), autoRemoveCount); + std::string autocmd = "apt autoremove"; + if (getenv("SUDO_USER") != nullptr) + { + auto const envsudocmd = getenv("SUDO_COMMAND"); + auto const envshell = getenv("SHELL"); + if (envsudocmd == nullptr || envshell == nullptr || strcmp(envsudocmd, envshell) != 0) + autocmd = "sudo " + autocmd; + } + ioprintf(c1out, P_("Use '%s' to remove it.", "Use '%s' to remove them.", autoRemoveCount), autocmd.c_str()); + c1out << std::endl; + } + return true; +} + /*}}}*/ +// DoCacheManipulationFromCommandLine /*{{{*/ +static const unsigned short MOD_REMOVE = 1; +static const unsigned short MOD_INSTALL = 2; + +bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector<PseudoPkg> &VolatileCmdL, CacheFile &Cache, int UpgradeMode, + APT::PackageVector &HeldBackPackages) +{ + std::map<unsigned short, APT::VersionVector> verset; + std::set<std::string> UnknownPackages; + return DoCacheManipulationFromCommandLine(CmdL, VolatileCmdL, Cache, verset, UpgradeMode, UnknownPackages, HeldBackPackages); +} +bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector<PseudoPkg> &VolatileCmdL, CacheFile &Cache, + std::map<unsigned short, APT::VersionVector> &verset, int UpgradeMode, + std::set<std::string> &UnknownPackages, APT::PackageVector &HeldBackPackages) +{ + // Enter the special broken fixing mode if the user specified arguments + bool BrokenFix = false; + if (Cache->BrokenCount() != 0) + BrokenFix = true; + + std::unique_ptr<pkgProblemResolver> Fix(nullptr); + if (_config->FindB("APT::Get::CallResolver", true) == true) + Fix.reset(new pkgProblemResolver(Cache)); + + unsigned short fallback = MOD_INSTALL; + if (strcasecmp(CmdL.FileList[0], "reinstall") == 0) + _config->Set("APT::Get::ReInstall", "true"); + else if (strcasecmp(CmdL.FileList[0],"remove") == 0) + fallback = MOD_REMOVE; + else if (strcasecmp(CmdL.FileList[0], "purge") == 0) + { + _config->Set("APT::Get::Purge", true); + fallback = MOD_REMOVE; + } + else if (strcasecmp(CmdL.FileList[0], "autoremove") == 0 || + strcasecmp(CmdL.FileList[0], "auto-remove") == 0) + { + _config->Set("APT::Get::AutomaticRemove", "true"); + fallback = MOD_REMOVE; + } + else if (strcasecmp(CmdL.FileList[0], "autopurge") == 0) + { + _config->Set("APT::Get::AutomaticRemove", "true"); + _config->Set("APT::Get::Purge", true); + fallback = MOD_REMOVE; + } + + std::list<APT::VersionSet::Modifier> mods; + mods.push_back(APT::VersionSet::Modifier(MOD_INSTALL, "+", + APT::VersionSet::Modifier::POSTFIX, APT::CacheSetHelper::CANDIDATE)); + mods.push_back(APT::VersionSet::Modifier(MOD_REMOVE, "-", + APT::VersionSet::Modifier::POSTFIX, APT::CacheSetHelper::NEWEST)); + CacheSetHelperAPTGet helper(c0out); + verset = APT::VersionVector::GroupedFromCommandLine(Cache, + CmdL.FileList + 1, mods, fallback, helper); + for (auto &vs : verset) + { + std::set<map_id_t> seen; + vs.second.erase(std::remove_if(vs.second.begin(), vs.second.end(), [&](auto const &p) { return not seen.insert(p->ID).second; }), vs.second.end()); + } + + for (auto const &I: VolatileCmdL) + { + pkgCache::PkgIterator const P = Cache->FindPkg(I.name); + if (P.end()) + continue; + + // Set any version providing the .deb as the candidate. + for (auto Prv = P.ProvidesList(); Prv.end() == false; Prv++) + { + if (I.release.empty()) + Cache.GetDepCache()->SetCandidateVersion(Prv.OwnerVer()); + else + Cache.GetDepCache()->SetCandidateRelease(Prv.OwnerVer(), I.release); + } + + // via cacheset to have our usual virtual handling + APT::VersionContainerInterface::FromPackage(&(verset[MOD_INSTALL]), Cache, P, APT::CacheSetHelper::CANDIDATE, helper); + } + + UnknownPackages = helper.notFound; + + if (_error->PendingError() == true) + { + helper.showVirtualPackageErrors(Cache); + return false; + } + + + TryToInstall InstallAction(Cache, Fix.get(), BrokenFix); + TryToRemove RemoveAction(Cache, Fix.get()); + APT::PackageSet UpgradablePackages; + + { + unsigned short const order[] = { MOD_REMOVE, MOD_INSTALL, 0 }; + + for (unsigned short i = 0; order[i] != 0; ++i) + { + if (order[i] == MOD_INSTALL) + InstallAction = std::for_each(verset[MOD_INSTALL].begin(), verset[MOD_INSTALL].end(), InstallAction); + else if (order[i] == MOD_REMOVE) + RemoveAction = std::for_each(verset[MOD_REMOVE].begin(), verset[MOD_REMOVE].end(), RemoveAction); + } + + { + APT::CacheSetHelper helper; + helper.PackageFrom(APT::CacheSetHelper::PATTERN, &UpgradablePackages, Cache, "?upgradable"); + } + + if (Fix != NULL && _config->FindB("APT::Get::AutoSolving", true) == true) + { + InstallAction.propagateReleaseCandidateSwitching(helper.selectedByRelease, c0out); + InstallAction.doAutoInstall(); + } + + if (_error->PendingError() == true) + { + return false; + } + + /* If we are in the Broken fixing mode we do not attempt to fix the + problems. This is if the user invoked install without -f and gave + packages */ + if (BrokenFix == true && Cache->BrokenCount() != 0) + { + c1out << _("You might want to run 'apt --fix-broken install' to correct these.") << std::endl; + ShowBroken(c1out,Cache,false); + return _error->Error(_("Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution).")); + } + + if (Fix != NULL) + { + // Call the scored problem resolver + OpTextProgress Progress(*_config); + bool const distUpgradeMode = strcmp(CmdL.FileList[0], "dist-upgrade") == 0 || strcmp(CmdL.FileList[0], "full-upgrade") == 0; + + if (distUpgradeMode && _config->Find("Binary") == "apt") + _config->CndSet("APT::Get::AutomaticRemove::Kernels", _config->FindB("APT::Get::AutomaticRemove", true)); + + bool resolver_fail = false; + if (distUpgradeMode == true || UpgradeMode != APT::Upgrade::ALLOW_EVERYTHING) + resolver_fail = APT::Upgrade::Upgrade(Cache, UpgradeMode, &Progress); + else + resolver_fail = Fix->Resolve(true, &Progress); + + if (resolver_fail == false && Cache->BrokenCount() == 0) + return false; + } + + if (CheckNothingBroken(Cache) == false) + return false; + } + if (!DoAutomaticRemove(Cache)) + return false; + + // if nothing changed in the cache, but only the automark information + // we write the StateFile here, otherwise it will be written in + // cache.commit() + if (InstallAction.AutoMarkChanged > 0 && + Cache->DelCount() == 0 && Cache->InstCount() == 0 && + Cache->BadCount() == 0 && + _config->FindB("APT::Get::Simulate",false) == false) + Cache->writeStateFile(NULL); + + SortedPackageUniverse Universe(Cache); + for (auto const &Pkg: Universe) + if (Pkg->CurrentVer != 0 && not Cache[Pkg].Upgrade() && not Cache[Pkg].Delete() && + UpgradablePackages.find(Pkg) != UpgradablePackages.end()) + HeldBackPackages.push_back(Pkg); + + return true; +} + /*}}}*/ +bool AddVolatileSourceFile(pkgSourceList *const SL, PseudoPkg &&pkg, std::vector<PseudoPkg> &VolatileCmdL)/*{{{*/ +{ + auto const ext = flExtension(pkg.name); + if (ext != "dsc" && FileExists(pkg.name + "/debian/control") == false) + return false; + std::vector<std::string> files; + SL->AddVolatileFile(pkg.name, &files); + std::transform(files.begin(), files.end(), std::back_inserter(VolatileCmdL), [&](auto &&f) { return PseudoPkg{std::move(f), pkg.arch, pkg.release, pkg.index}; }); + return true; + +} + /*}}}*/ +bool AddVolatileBinaryFile(pkgSourceList *const SL, PseudoPkg &&pkg, std::vector<PseudoPkg> &VolatileCmdL)/*{{{*/ +{ + auto const ext = flExtension(pkg.name); + if (ext != "deb" && ext != "ddeb" && ext != "changes") + return false; + std::vector<std::string> files; + SL->AddVolatileFile(pkg.name, &files); + std::transform(files.begin(), files.end(), std::back_inserter(VolatileCmdL), [&](auto &&f) { return PseudoPkg{std::move(f), pkg.arch, pkg.release, pkg.index}; }); + return true; +} + /*}}}*/ +static bool AddIfVolatile(pkgSourceList *const SL, std::vector<PseudoPkg> &VolatileCmdL, bool (*Add)(pkgSourceList *const, PseudoPkg &&, std::vector<PseudoPkg> &), char const * const I, std::string const &pseudoArch)/*{{{*/ +{ + if (I != nullptr && (I[0] == '/' || (I[0] == '.' && (I[1] == '\0' || (I[1] == '.' && (I[2] == '\0' || I[2] == '/')) || I[1] == '/')))) + { + PseudoPkg pkg(I, pseudoArch, "", SL->GetVolatileFiles().size()); + if (FileExists(I)) // this accepts directories and symlinks, too + { + if (Add(SL, std::move(pkg), VolatileCmdL)) + ; + else + _error->Error(_("Unsupported file %s given on commandline"), I); + return true; + } + else + { + auto const found = pkg.name.rfind("/"); + if (found == pkg.name.find("/")) + _error->Error(_("Unsupported file %s given on commandline"), I); + else + { + pkg.release = pkg.name.substr(found + 1); + pkg.name.erase(found); + if (Add(SL, std::move(pkg), VolatileCmdL)) + ; + else + _error->Error(_("Unsupported file %s given on commandline"), I); + } + return true; + } + } + return false; +} + /*}}}*/ +std::vector<PseudoPkg> GetAllPackagesAsPseudo(pkgSourceList *const SL, CommandLine &CmdL, bool (*Add)(pkgSourceList *const, PseudoPkg &&, std::vector<PseudoPkg> &), std::string const &pseudoArch)/*{{{*/ +{ + std::vector<PseudoPkg> PkgCmdL; + std::for_each(CmdL.FileList + 1, CmdL.FileList + CmdL.FileSize(), [&](char const *const I) { + if (AddIfVolatile(SL, PkgCmdL, Add, I, pseudoArch) == false) + PkgCmdL.emplace_back(I, pseudoArch, "", -1); + }); + return PkgCmdL; +} + /*}}}*/ +std::vector<PseudoPkg> GetPseudoPackages(pkgSourceList *const SL, CommandLine &CmdL, bool (*Add)(pkgSourceList *const, PseudoPkg &&, std::vector<PseudoPkg> &), std::string const &pseudoArch)/*{{{*/ +{ + std::vector<PseudoPkg> VolatileCmdL; + std::remove_if(CmdL.FileList + 1, CmdL.FileList + 1 + CmdL.FileSize(), [&](char const *const I) { + return AddIfVolatile(SL, VolatileCmdL, Add, I, pseudoArch); + }); + return VolatileCmdL; +} + /*}}}*/ +// DoInstall - Install packages from the command line /*{{{*/ +// --------------------------------------------------------------------- +/* Install named packages */ +struct PkgIsExtraInstalled { + pkgCacheFile * const Cache; + APT::VersionVector const * const verset; + PkgIsExtraInstalled(pkgCacheFile * const Cache, APT::VersionVector const * const Container) : Cache(Cache), verset(Container) {} + bool operator() (pkgCache::PkgIterator const &Pkg) + { + if ((*Cache)[Pkg].Install() == false) + return false; + pkgCache::VerIterator const Cand = (*Cache)[Pkg].CandidateVerIter(*Cache); + return std::find(verset->begin(), verset->end(), Cand) == verset->end(); + } +}; +bool DoInstall(CommandLine &CmdL) +{ + CacheFile Cache; + Cache.InhibitActionGroups(true); + if (Cache.BuildSourceList() == false) + return false; + auto VolatileCmdL = GetPseudoPackages(Cache.GetSourceList(), CmdL, AddVolatileBinaryFile, ""); + + // then open the cache + if (Cache.OpenForInstall() == false || + Cache.CheckDeps(CmdL.FileSize() != 1) == false) + return false; + + std::map<unsigned short, APT::VersionVector> verset; + std::set<std::string> UnknownPackages; + APT::PackageVector HeldBackPackages; + + if (not DoCacheManipulationFromCommandLine(CmdL, VolatileCmdL, Cache, verset, 0, UnknownPackages, HeldBackPackages)) + { + RunJsonHook("AptCli::Hooks::Install", "org.debian.apt.hooks.install.fail", CmdL.FileList, Cache, UnknownPackages); + return false; + } + + /* Print out a list of packages that are going to be installed extra + to what the user asked */ + SortedPackageUniverse Universe(Cache); + if (Cache->InstCount() != verset[MOD_INSTALL].size()) + ShowList(c1out, _("The following additional packages will be installed:"), Universe, + PkgIsExtraInstalled(&Cache, &verset[MOD_INSTALL]), + &PrettyFullName, CandidateVersion(&Cache)); + + /* Print out a list of suggested and recommended packages */ + { + std::list<std::string> Recommends, Suggests, SingleRecommends, SingleSuggests; + for (auto const &Pkg: Universe) + { + /* Just look at the ones we want to install */ + if ((*Cache)[Pkg].Install() == false) + continue; + + // get the recommends/suggests for the candidate ver + pkgCache::VerIterator CV = (*Cache)[Pkg].CandidateVerIter(*Cache); + for (pkgCache::DepIterator D = CV.DependsList(); D.end() == false; ) + { + pkgCache::DepIterator Start; + pkgCache::DepIterator End; + D.GlobOr(Start,End); // advances D + if (Start->Type != pkgCache::Dep::Recommends && Start->Type != pkgCache::Dep::Suggests) + continue; + + { + // Skip if we already saw this + std::string target; + for (pkgCache::DepIterator I = Start; I != D; ++I) + { + if (target.empty() == false) + target.append(" | "); + target.append(I.TargetPkg().FullName(true)); + } + std::list<std::string> &Type = Start->Type == pkgCache::Dep::Recommends ? SingleRecommends : SingleSuggests; + if (std::find(Type.begin(), Type.end(), target) != Type.end()) + continue; + Type.push_back(target); + } + + std::list<std::string> OrList; + bool foundInstalledInOrGroup = false; + for (pkgCache::DepIterator I = Start; I != D; ++I) + { + { + // satisfying package is installed and not marked for deletion + APT::VersionList installed = APT::VersionList::FromDependency(Cache, I, APT::CacheSetHelper::INSTALLED); + if (std::find_if(installed.begin(), installed.end(), + [&Cache](pkgCache::VerIterator const &Ver) { return Cache[Ver.ParentPkg()].Delete() == false; }) != installed.end()) + { + foundInstalledInOrGroup = true; + break; + } + } + + { + // satisfying package is upgraded to/new install + APT::VersionList upgrades = APT::VersionList::FromDependency(Cache, I, APT::CacheSetHelper::CANDIDATE); + if (std::find_if(upgrades.begin(), upgrades.end(), + [&Cache](pkgCache::VerIterator const &Ver) { return Cache[Ver.ParentPkg()].Upgrade(); }) != upgrades.end()) + { + foundInstalledInOrGroup = true; + break; + } + } + + if (OrList.empty()) + OrList.push_back(I.TargetPkg().FullName(true)); + else + OrList.push_back("| " + I.TargetPkg().FullName(true)); + } + + if(foundInstalledInOrGroup == false) + { + std::list<std::string> &Type = Start->Type == pkgCache::Dep::Recommends ? Recommends : Suggests; + std::move(OrList.begin(), OrList.end(), std::back_inserter(Type)); + } + } + } + auto always_true = [](std::string const&) { return true; }; + auto string_ident = [](std::string const&str) { return str; }; + auto verbose_show_candidate = + [&Cache](std::string str) + { + if (APT::String::Startswith(str, "| ")) + str.erase(0, 2); + pkgCache::PkgIterator const Pkg = Cache->FindPkg(str); + if (Pkg.end() == true) + return ""; + return (*Cache)[Pkg].CandVersion; + }; + ShowList(c1out,_("Suggested packages:"), Suggests, + always_true, string_ident, verbose_show_candidate); + ShowList(c1out,_("Recommended packages:"), Recommends, + always_true, string_ident, verbose_show_candidate); + } + + RunJsonHook("AptCli::Hooks::Install", "org.debian.apt.hooks.install.pre-prompt", CmdL.FileList, Cache); + + bool result; + // See if we need to prompt + // FIXME: check if really the packages in the set are going to be installed + if (Cache->InstCount() == verset[MOD_INSTALL].size() && Cache->DelCount() == 0) + result = InstallPackages(Cache, HeldBackPackages, false, false, true, "AptCli::Hooks::Install", CmdL); + else + result = InstallPackages(Cache, HeldBackPackages, false, true, true, "AptCli::Hooks::Install", CmdL); + + if (result) + result = RunJsonHook("AptCli::Hooks::Install", "org.debian.apt.hooks.install.post", CmdL.FileList, Cache); + else + /* not a result */ RunJsonHook("AptCli::Hooks::Install", "org.debian.apt.hooks.install.fail", CmdL.FileList, Cache); + + return result; +} + /*}}}*/ + +// TryToInstall - Mark a package for installation /*{{{*/ +void TryToInstall::operator() (pkgCache::VerIterator const &Ver) { + if (unlikely(Ver.end())) + { + _error->Fatal("The given version to TryToInstall is invalid!"); + return; + } + pkgCache::PkgIterator Pkg = Ver.ParentPkg(); + if (unlikely(Pkg.end())) + { + _error->Fatal("The given version to TryToInstall has an invalid parent package!"); + return; + } + + Cache->GetDepCache()->SetCandidateVersion(Ver); + pkgDepCache::StateCache &State = (*Cache)[Pkg]; + + // Handle the no-upgrade case + if (_config->FindB("APT::Get::upgrade",true) == false && Pkg->CurrentVer != 0) + ioprintf(c1out,_("Skipping %s, it is already installed and upgrade is not set.\n"), + Pkg.FullName(true).c_str()); + // Ignore request for install if package would be new + else if (_config->FindB("APT::Get::Only-Upgrade", false) == true && Pkg->CurrentVer == 0) + ioprintf(c1out,_("Skipping %s, it is not installed and only upgrades are requested.\n"), + Pkg.FullName(true).c_str()); + else { + if (Fix != NULL) { + Fix->Clear(Pkg); + Fix->Protect(Pkg); + } + Cache->GetDepCache()->MarkInstall(Pkg,false); + + if (State.Install() == false) { + if (_config->FindB("APT::Get::ReInstall",false) == true) { + if (Pkg->CurrentVer == 0 || Pkg.CurrentVer().Downloadable() == false) + ioprintf(c1out,_("Reinstallation of %s is not possible, it cannot be downloaded.\n"), + Pkg.FullName(true).c_str()); + else + Cache->GetDepCache()->SetReInstall(Pkg, true); + } else + // TRANSLATORS: First string is package name, second is version + ioprintf(c1out,_("%s is already the newest version (%s).\n"), + Pkg.FullName(true).c_str(), Pkg.CurrentVer().VerStr()); + } + + // Install it with autoinstalling enabled (if we not respect the minial + // required deps or the policy) + if (FixBroken == false) + doAutoInstallLater.insert(Pkg); + } + + // see if we need to fix the auto-mark flag + // e.g. apt-get install foo + // where foo is marked automatic + if (State.Install() == false && + (State.Flags & pkgCache::Flag::Auto) && + _config->FindB("APT::Get::ReInstall",false) == false && + _config->FindB("APT::Get::Only-Upgrade",false) == false && + _config->FindB("APT::Get::Download-Only",false) == false) + { + ioprintf(c1out,_("%s set to manually installed.\n"), + Pkg.FullName(true).c_str()); + Cache->GetDepCache()->MarkAuto(Pkg,false); + AutoMarkChanged++; + } +} + /*}}}*/ +bool TryToInstall::propagateReleaseCandidateSwitching(std::list<std::pair<pkgCache::VerIterator, std::string> > const &start, std::ostream &out)/*{{{*/ +{ + for (std::list<std::pair<pkgCache::VerIterator, std::string> >::const_iterator s = start.begin(); + s != start.end(); ++s) + Cache->GetDepCache()->SetCandidateVersion(s->first); + + bool Success = true; + // the Changed list contains: + // first: "new version" + // second: "what-caused the change" + std::list<std::pair<pkgCache::VerIterator, pkgCache::VerIterator> > Changed; + for (std::list<std::pair<pkgCache::VerIterator, std::string> >::const_iterator s = start.begin(); + s != start.end(); ++s) + { + Changed.push_back(std::make_pair(s->first, pkgCache::VerIterator(*Cache))); + // We continue here even if it failed to enhance the ShowBroken output + Success &= Cache->GetDepCache()->SetCandidateRelease(s->first, s->second, Changed); + } + for (std::list<std::pair<pkgCache::VerIterator, pkgCache::VerIterator> >::const_iterator c = Changed.begin(); + c != Changed.end(); ++c) + { + if (c->second.end() == true) + { + auto const pkgname = c->first.ParentPkg().FullName(true); + if (APT::String::Startswith(pkgname, "builddeps:")) + continue; + ioprintf(out, _("Selected version '%s' (%s) for '%s'\n"), + c->first.VerStr(), c->first.RelStr().c_str(), pkgname.c_str()); + } + else if (c->first.ParentPkg()->Group != c->second.ParentPkg()->Group) + { + auto pkgname = c->second.ParentPkg().FullName(true); + if (APT::String::Startswith(pkgname, "builddeps:")) + pkgname.replace(0, strlen("builddeps"), "src"); + pkgCache::VerIterator V = (*Cache)[c->first.ParentPkg()].CandidateVerIter(*Cache); + ioprintf(out, _("Selected version '%s' (%s) for '%s' because of '%s'\n"), V.VerStr(), + V.RelStr().c_str(), V.ParentPkg().FullName(true).c_str(), pkgname.c_str()); + } + } + return Success; +} + /*}}}*/ +void TryToInstall::doAutoInstall() { /*{{{*/ + auto * const DCache = Cache->GetDepCache(); + for (auto const &P: doAutoInstallLater) + DCache->MarkInstall(P, true); + doAutoInstallLater.clear(); +} + /*}}}*/ +// TryToRemove - Mark a package for removal /*{{{*/ +void TryToRemove::operator() (pkgCache::VerIterator const &Ver) +{ + pkgCache::PkgIterator Pkg = Ver.ParentPkg(); + + if (Fix != NULL) + { + Fix->Clear(Pkg); + Fix->Protect(Pkg); + Fix->Remove(Pkg); + } + + if ((Pkg->CurrentVer == 0 && PurgePkgs == false) || + (PurgePkgs == true && Pkg->CurrentState == pkgCache::State::NotInstalled)) + { + pkgCache::GrpIterator Grp = Pkg.Group(); + pkgCache::PkgIterator P = Grp.PackageList(); + for (; P.end() != true; P = Grp.NextPkg(P)) + { + if (P == Pkg) + continue; + if (P->CurrentVer != 0 || (PurgePkgs == true && P->CurrentState != pkgCache::State::NotInstalled)) + { + // TRANSLATORS: Note, this is not an interactive question + ioprintf(c1out,_("Package '%s' is not installed, so not removed. Did you mean '%s'?\n"), + Pkg.FullName(true).c_str(), P.FullName(true).c_str()); + break; + } + } + if (P.end() == true) + ioprintf(c1out,_("Package '%s' is not installed, so not removed\n"),Pkg.FullName(true).c_str()); + + // MarkInstall refuses to install packages on hold + Pkg->SelectedState = pkgCache::State::Hold; + } + else + Cache->GetDepCache()->MarkDelete(Pkg, PurgePkgs); +} + /*}}}*/ diff --git a/apt-private/private-install.h b/apt-private/private-install.h new file mode 100644 index 0000000..0f6d048 --- /dev/null +++ b/apt-private/private-install.h @@ -0,0 +1,81 @@ +#ifndef APT_PRIVATE_INSTALL_H +#define APT_PRIVATE_INSTALL_H + +#include <apt-pkg/depcache.h> +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#include <list> +#include <string> +#include <utility> + +class CacheFile; +class CommandLine; +class pkgProblemResolver; + +APT_PUBLIC bool DoInstall(CommandLine &Cmd); + +struct PseudoPkg +{ + std::string name; + std::string arch; + std::string release; + ssize_t index; + PseudoPkg(std::string const &n, std::string const &a, std::string const &r) : name(n), arch(a), release(r), index(-1) {} + PseudoPkg(std::string const &n, std::string const &a, std::string const &r, ssize_t i) : name(n), arch(a), release(r), index(i) {} +}; +std::vector<PseudoPkg> GetAllPackagesAsPseudo(pkgSourceList *const SL, CommandLine &CmdL, bool (*Add)(pkgSourceList *const, PseudoPkg &&, std::vector<PseudoPkg> &), std::string const &pseudoArch); +std::vector<PseudoPkg> GetPseudoPackages(pkgSourceList *const SL, CommandLine &CmdL, bool (*Add)(pkgSourceList *const, PseudoPkg &&, std::vector<PseudoPkg> &), std::string const &pseudoArch); +bool AddVolatileBinaryFile(pkgSourceList *const SL, PseudoPkg &&pkg, std::vector<PseudoPkg> &VolatileCmdL); +bool AddVolatileSourceFile(pkgSourceList *const SL, PseudoPkg &&pkg, std::vector<PseudoPkg> &VolatileCmdL); + +bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector<PseudoPkg> &VolatileCmdL, CacheFile &Cache, + std::map<unsigned short, APT::VersionVector> &verset, int UpgradeMode, + std::set<std::string> &UnknownPackages, APT::PackageVector &HeldBackPackages); +bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector<PseudoPkg> &VolatileCmdL, CacheFile &Cache, int UpgradeMode, + APT::PackageVector &HeldBackPackages); + +APT_PUBLIC bool InstallPackages(CacheFile &Cache, + APT::PackageVector &HeldBackPackages, + bool ShwKept, bool Ask = true, + bool Safety = true, + std::string const &Hook = "", + CommandLine const &CmdL = {}); + +bool CheckNothingBroken(CacheFile &Cache); +bool DoAutomaticRemove(CacheFile &Cache); + +// TryToInstall - Mark a package for installation /*{{{*/ +struct TryToInstall { + pkgCacheFile* Cache; + pkgProblemResolver* Fix; + bool FixBroken; + unsigned long AutoMarkChanged; + APT::PackageVector doAutoInstallLater; + + TryToInstall(pkgCacheFile &Cache, pkgProblemResolver *PM, bool const FixBroken) : Cache(&Cache), Fix(PM), + FixBroken(FixBroken), AutoMarkChanged(0) {}; + + void operator() (pkgCache::VerIterator const &Ver); + bool propagateReleaseCandidateSwitching(std::list<std::pair<pkgCache::VerIterator, std::string> > const &start, std::ostream &out); + void doAutoInstall(); +}; + /*}}}*/ +// TryToRemove - Mark a package for removal /*{{{*/ +struct TryToRemove { + pkgCacheFile* Cache; + pkgProblemResolver* Fix; + bool PurgePkgs; + + TryToRemove(pkgCacheFile &Cache, pkgProblemResolver *PM) : Cache(&Cache), Fix(PM), + PurgePkgs(_config->FindB("APT::Get::Purge", false)) {}; + + void operator() (pkgCache::VerIterator const &Ver); +}; + /*}}}*/ + + +#endif diff --git a/apt-private/private-json-hooks.cc b/apt-private/private-json-hooks.cc new file mode 100644 index 0000000..ddf1c89 --- /dev/null +++ b/apt-private/private-json-hooks.cc @@ -0,0 +1,532 @@ +/* + * private-json-hooks.cc - 2nd generation, JSON-RPC, hooks for APT + * + * Copyright (c) 2018 Canonical Ltd + * + * SPDX-License-Identifier: GPL-2.0+ + */ +#include <config.h> + +#include <apt-pkg/debsystem.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/strutl.h> +#include <apt-private/private-json-hooks.h> +#include <apt-private/private-output.h> + +#include <iomanip> +#include <ostream> +#include <sstream> +#include <stack> +#include <unordered_map> + +#include <signal.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +/** + * @brief Simple JSON writer + * + * This performs no error checking, so be careful. + */ +class APT_HIDDEN JsonWriter +{ + std::ostream &os; + std::locale old_locale; + + enum write_state + { + empty, + in_array_first_element, + in_array, + in_object_first_key, + in_object_key, + in_object_val + } state = empty; + + std::stack<write_state> old_states; + + void maybeComma() + { + switch (state) + { + case empty: + break; + case in_object_val: + state = in_object_key; + break; + case in_object_key: + state = in_object_val; + os << ','; + break; + case in_array: + os << ','; + break; + case in_array_first_element: + state = in_array; + break; + case in_object_first_key: + state = in_object_val; + break; + default: + abort(); + } + } + + void pushState(write_state state) + { + old_states.push(this->state); + this->state = state; + } + + void popState() + { + this->state = old_states.top(); + old_states.pop(); + } + + public: + explicit JsonWriter(std::ostream &os) : os(os), old_locale{os.imbue(std::locale::classic())} {} + ~JsonWriter() { os.imbue(old_locale); } + JsonWriter &beginArray() + { + maybeComma(); + pushState(in_array_first_element); + os << '['; + return *this; + } + JsonWriter &endArray() + { + popState(); + os << ']'; + return *this; + } + JsonWriter &beginObject() + { + maybeComma(); + pushState(in_object_first_key); + os << '{'; + return *this; + } + JsonWriter &endObject() + { + popState(); + os << '}'; + return *this; + } + std::ostream &encodeString(std::ostream &out, std::string const &str) + { + out << '"'; + + for (std::string::const_iterator c = str.begin(); c != str.end(); c++) + { + if (*c <= 0x1F || *c == '"' || *c == '\\') + ioprintf(out, "\\u%04X", *c); + else + out << *c; + } + + out << '"'; + return out; + } + JsonWriter &name(std::string const &name) + { + maybeComma(); + encodeString(os, name) << ':'; + return *this; + } + JsonWriter &value(std::string const &value) + { + maybeComma(); + encodeString(os, value); + return *this; + } + JsonWriter &value(const char *value) + { + maybeComma(); + if (value == nullptr) + os << "null"; + else + encodeString(os, value); + return *this; + } + JsonWriter &value(int value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(long value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(long long value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(unsigned long long value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(unsigned long value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(unsigned int value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(bool value) + { + maybeComma(); + os << (value ? "true" : "false"); + return *this; + } + JsonWriter &value(double value) + { + maybeComma(); + os << value; + return *this; + } +}; + +/** + * @brief Write a VerFileIterator to a JsonWriter + */ +static void verFiletoJson(JsonWriter &writer, CacheFile &, pkgCache::VerFileIterator const &vf) +{ + auto pf = vf.File(); // Packages file + auto rf = pf.ReleaseFile(); // release file + + writer.beginObject(); + if (not rf.end()) { + if (rf->Archive != 0) + writer.name("archive").value(rf.Archive()); + if (rf->Codename != 0) + writer.name("codename").value(rf.Codename()); + if (rf->Version != 0) + writer.name("version").value(rf.Version()); + if (rf->Origin != 0) + writer.name("origin").value(rf.Origin()); + if (rf->Label != 0) + writer.name("label").value(rf.Label()); + if (rf->Site != 0) + writer.name("site").value(rf.Site()); + } + + writer.endObject(); +} + +/** + * @brief Write a VerIterator to a JsonWriter + */ +static void verIterToJson(JsonWriter &writer, CacheFile &Cache, pkgCache::VerIterator const &Ver) +{ + writer.beginObject(); + writer.name("id").value(Ver->ID); + writer.name("version").value(Ver.VerStr()); + writer.name("architecture").value(Ver.Arch()); + writer.name("pin").value(Cache->GetPolicy().GetPriority(Ver)); + + writer.name("origins"); + writer.beginArray(); + for (auto vf = Ver.FileList(); !vf.end(); vf++) + if ((vf.File()->Flags & pkgCache::Flag::NotSource) == 0) + verFiletoJson(writer, Cache, vf); + writer.endArray(); + + writer.endObject(); +} + +/** + * @brief Copy of debSystem::DpkgChrootDirectory() + * @todo Remove + */ +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); +} + +/** + * @brief Send a notification to the hook's stream + */ +static void NotifyHook(std::ostream &os, std::string const &method, const char **FileList, CacheFile &Cache, std::set<std::string> const &UnknownPackages, int hookVersion) +{ + SortedPackageUniverse Universe(Cache); + JsonWriter jsonWriter{os}; + + jsonWriter.beginObject(); + + jsonWriter.name("jsonrpc").value("2.0"); + jsonWriter.name("method").value(method); + + /* Build params */ + jsonWriter.name("params").beginObject(); + if (FileList != nullptr) + { + jsonWriter.name("command").value(FileList[0]); + jsonWriter.name("search-terms").beginArray(); + for (int i = 1; FileList[i] != NULL; i++) + jsonWriter.value(FileList[i]); + jsonWriter.endArray(); + } + jsonWriter.name("unknown-packages").beginArray(); + for (auto const &PkgName : UnknownPackages) + jsonWriter.value(PkgName); + jsonWriter.endArray(); + + jsonWriter.name("packages").beginArray(); + for (auto const &Pkg : Universe) + { + switch (Cache[Pkg].Mode) + { + case pkgDepCache::ModeInstall: + case pkgDepCache::ModeDelete: + break; + default: + continue; + } + + jsonWriter.beginObject(); + + jsonWriter.name("id").value(Pkg->ID); + jsonWriter.name("name").value(Pkg.Name()); + jsonWriter.name("architecture").value(Pkg.Arch()); + + switch (Cache[Pkg].Mode) + { + case pkgDepCache::ModeInstall: + if (Pkg->CurrentVer != 0 && Cache[Pkg].Upgrade() && hookVersion >= 0x020) + jsonWriter.name("mode").value("upgrade"); + else if (Pkg->CurrentVer != 0 && Cache[Pkg].Downgrade() && hookVersion >= 0x020) + jsonWriter.name("mode").value("downgrade"); + else if (Pkg->CurrentVer != 0 && Cache[Pkg].ReInstall() && hookVersion >= 0x020) + jsonWriter.name("mode").value("reinstall"); + else + jsonWriter.name("mode").value("install"); + break; + case pkgDepCache::ModeDelete: + jsonWriter.name("mode").value(Cache[Pkg].Purge() ? "purge" : "deinstall"); + break; + default: + continue; + } + jsonWriter.name("automatic").value(bool(Cache[Pkg].Flags & pkgCache::Flag::Auto)); + + jsonWriter.name("versions").beginObject(); + + if (Cache[Pkg].CandidateVer != nullptr) + verIterToJson(jsonWriter.name("candidate"), Cache, Cache[Pkg].CandidateVerIter(Cache)); + if (Cache[Pkg].InstallVer != nullptr) + verIterToJson(jsonWriter.name("install"), Cache, Cache[Pkg].InstVerIter(Cache)); + if (Pkg->CurrentVer != 0) + verIterToJson(jsonWriter.name("current"), Cache, Pkg.CurrentVer()); + + jsonWriter.endObject(); + + jsonWriter.endObject(); + } + + jsonWriter.endArray(); // packages + jsonWriter.endObject(); // params + jsonWriter.endObject(); // main +} + +/// @brief Build the hello handshake message for 0.1 protocol +static std::string BuildHelloMessage() +{ + std::stringstream Hello; + JsonWriter(Hello).beginObject().name("jsonrpc").value("2.0").name("method").value("org.debian.apt.hooks.hello").name("id").value(0).name("params").beginObject().name("versions").beginArray().value("0.1").value("0.2").endArray().endObject().endObject(); + + return Hello.str(); +} + +/// @brief Build the bye notification for 0.1 protocol +static std::string BuildByeMessage() +{ + std::stringstream Bye; + JsonWriter(Bye).beginObject().name("jsonrpc").value("2.0").name("method").value("org.debian.apt.hooks.bye").name("params").beginObject().endObject().endObject(); + + return Bye.str(); +} + +/// @brief Run the Json hook processes in the given option. +bool RunJsonHook(std::string const &option, std::string const &method, const char **FileList, CacheFile &Cache, std::set<std::string> const &UnknownPackages) +{ + std::unordered_map<int, std::string> notifications; + std::string HelloData = BuildHelloMessage(); + std::string ByeData = BuildByeMessage(); + int hookVersion; + + bool result = true; + + Configuration::Item const *Opts = _config->Tree(option.c_str()); + if (Opts == 0 || Opts->Child == 0) + return true; + Opts = Opts->Child; + + // Flush output before calling hooks + std::clog.flush(); + std::cerr.flush(); + std::cout.flush(); + c2out.flush(); + c1out.flush(); + + sighandler_t old_sigpipe = signal(SIGPIPE, SIG_IGN); + sighandler_t old_sigint = signal(SIGINT, SIG_IGN); + sighandler_t old_sigquit = signal(SIGQUIT, 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; + + // Create the pipes + std::set<int> KeepFDs; + MergeKeepFdsFromConfiguration(KeepFDs); + int Pipes[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, Pipes) != 0) + { + result = _error->Errno("pipe", "Failed to create IPC pipe to subprocess"); + break; + } + + int InfoFD = 3; + + if (InfoFD != 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); + + std::string hookfd; + strprintf(hookfd, "%d", InfoFD); + setenv("APT_HOOK_SOCKET", 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", "Failed to open new FD"); + break; + } + + fwrite(HelloData.data(), HelloData.size(), 1, F); + fwrite("\n\n", 2, 1, F); + fflush(F); + + char *line = nullptr; + size_t linesize = 0; + ssize_t size = getline(&line, &linesize, F); + + if (size < 0) + { + if (errno != ECONNRESET && errno != EPIPE) + _error->Error("Could not read response to hello message from hook %s: %s", Opts->Value.c_str(), strerror(errno)); + goto out; + } + else if (strstr(line, "error") != nullptr) + { + _error->Error("Hook %s reported an error during hello: %s", Opts->Value.c_str(), line); + goto out; + } + + if (strstr(line, "\"0.1\"")) + { + hookVersion = 0x010; + } + else if (strstr(line, "\"0.2\"")) + { + hookVersion = 0x020; + } + else + { + _error->Error("Unknown hook version in handshake from hook %s: %s", Opts->Value.c_str(), line); + goto out; + } + + size = getline(&line, &linesize, F); + if (size < 0) + { + _error->Error("Could not read message separator line after handshake from %s: %s", Opts->Value.c_str(), feof(F) ? "end of file" : strerror(errno)); + goto out; + } + else if (size == 0 || line[0] != '\n') + { + _error->Error("Expected empty line after handshake from %s, received %s", Opts->Value.c_str(), line); + goto out; + } + { + std::string &data = notifications[hookVersion]; + if (data.empty()) + { + std::stringstream ss; + NotifyHook(ss, method, FileList, Cache, UnknownPackages, hookVersion); + ; + data = ss.str(); + } + fwrite(data.data(), data.size(), 1, F); + fwrite("\n\n", 2, 1, F); + } + + fwrite(ByeData.data(), ByeData.size(), 1, F); + fwrite("\n\n", 2, 1, F); + out: + fclose(F); + // Clean up the sub process + if (ExecWait(Process, Opts->Value.c_str()) == false) + { + result = _error->Error("Failure running hook %s", Opts->Value.c_str()); + break; + } + + } + signal(SIGINT, old_sigint); + signal(SIGPIPE, old_sigpipe); + signal(SIGQUIT, old_sigquit); + + return result; +} diff --git a/apt-private/private-json-hooks.h b/apt-private/private-json-hooks.h new file mode 100644 index 0000000..41be295 --- /dev/null +++ b/apt-private/private-json-hooks.h @@ -0,0 +1,14 @@ +/* + * private-json-hooks.h - 2nd generation, JSON-RPC, hooks for APT + * + * Copyright (c) 2018 Canonical Ltd + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <set> +#include <string> + +#include <apt-private/private-cachefile.h> + +bool RunJsonHook(std::string const &option, std::string const &method, const char **FileList, CacheFile &Cache, std::set<std::string> const &UnknownPackages = {}); diff --git a/apt-private/private-list.cc b/apt-private/private-list.cc new file mode 100644 index 0000000..eee657c --- /dev/null +++ b/apt-private/private-list.cc @@ -0,0 +1,165 @@ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cachefilter.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/progress.h> +#include <apt-pkg/strutl.h> + +#include <apt-private/private-cacheset.h> +#include <apt-private/private-list.h> +#include <apt-private/private-output.h> + +#include <iostream> +#include <map> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include <apti18n.h> + /*}}}*/ + +struct PackageSortAlphabetic /*{{{*/ +{ + bool operator () (const pkgCache::PkgIterator &p_lhs, + const pkgCache::PkgIterator &p_rhs) + { + const std::string &l_name = p_lhs.FullName(true); + const std::string &r_name = p_rhs.FullName(true); + return (l_name < r_name); + } +}; + +class PackageNameMatcher : public Matcher +{ + static constexpr const char *const isfnmatch_strict = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.:*"; + pkgCacheFile &cacheFile; + public: + explicit PackageNameMatcher(pkgCacheFile &cacheFile, const char **patterns) + : cacheFile(cacheFile) + { + for(int i=0; patterns[i] != NULL; ++i) + { + std::string pattern = patterns[i]; + APT::CacheFilter::Matcher *cachefilter = NULL; + if(_config->FindB("APT::Cmd::Use-Regexp", false) == true) + cachefilter = new APT::CacheFilter::PackageNameMatchesRegEx(pattern); + else if (pattern.find_first_not_of(isfnmatch_strict) == std::string::npos) + cachefilter = new APT::CacheFilter::PackageNameMatchesFnmatch(pattern); + else + cachefilter = APT::CacheFilter::ParsePattern(pattern, &cacheFile).release(); + + if (cachefilter == nullptr) { + return; + filters.clear(); + } + filters.push_back(cachefilter); + } + } + virtual ~PackageNameMatcher() + { + for(J=filters.begin(); J != filters.end(); ++J) + delete *J; + } + virtual bool operator () (const pkgCache::PkgIterator &P) APT_OVERRIDE + { + for(J=filters.begin(); J != filters.end(); ++J) + { + APT::CacheFilter::Matcher *cachefilter = *J; + if((*cachefilter)(P)) + return true; + } + return false; + } + +private: + std::vector<APT::CacheFilter::Matcher*> filters; + std::vector<APT::CacheFilter::Matcher*>::const_iterator J; + #undef PackageMatcher +}; + /*}}}*/ +static void ListAllVersions(pkgCacheFile &CacheFile, pkgRecords &records,/*{{{*/ + pkgCache::PkgIterator const &P, std::ostream &outs, + std::string const &format) +{ + for (pkgCache::VerIterator Ver = P.VersionList(); + Ver.end() == false; ++Ver) + { + ListSingleVersion(CacheFile, records, Ver, outs, format); + outs << std::endl; + } +} + /*}}}*/ +// list - list package based on criteria /*{{{*/ +// --------------------------------------------------------------------- +bool DoList(CommandLine &Cmd) +{ + pkgCacheFile CacheFile; + pkgCache * const Cache = CacheFile.GetPkgCache(); + if (unlikely(Cache == nullptr || CacheFile.GetDepCache() == nullptr)) + return false; + pkgRecords records(CacheFile); + + const char **patterns; + const char *all_pattern[] = { "*", NULL}; + + if (strv_length(Cmd.FileList + 1) == 0) + { + patterns = all_pattern; + } else { + patterns = Cmd.FileList + 1; + } + + std::string format = "${color:highlight}${Package}${color:neutral}/${Origin} ${Version} ${Architecture}${ }${apt:Status}"; + if (_config->FindB("APT::Cmd::List-Include-Summary", false) == true) + format += "\n ${Description}\n"; + + PackageNameMatcher matcher(CacheFile, patterns); + LocalitySortedVersionSet bag; + OpTextProgress progress(*_config); + progress.OverallProgress(0, + Cache->Head().PackageCount, + Cache->Head().PackageCount, + _("Listing")); + GetLocalitySortedVersionSet(CacheFile, &bag, matcher, &progress); + bool const ShowAllVersions = _config->FindB("APT::Cmd::All-Versions", false); + std::map<std::string, std::string> output_map; + for (LocalitySortedVersionSet::iterator V = bag.begin(); V != bag.end(); ++V) + { + std::stringstream outs; + if(ShowAllVersions == true) + ListAllVersions(CacheFile, records, V.ParentPkg(), outs, format); + else + ListSingleVersion(CacheFile, records, V, outs, format); + output_map.insert(std::make_pair<std::string, std::string>( + V.ParentPkg().FullName(), outs.str())); + } + + // FIXME: SORT! and make sorting flexible (alphabetic, by pkg status) + // output the sorted map + std::map<std::string, std::string>::const_iterator K; + for (K = output_map.begin(); K != output_map.end(); ++K) + std::cout << (*K).second << std::endl; + + // be nice and tell the user if there is more to see + if (bag.size() == 1 && ShowAllVersions == false) + { + // start with -1 as we already displayed one version + int versions = -1; + pkgCache::VerIterator Ver = *bag.begin(); + for ( ; Ver.end() == false; ++Ver) + ++versions; + if (versions > 0) + _error->Notice(P_("There is %i additional version. Please use the '-a' switch to see it", "There are %i additional versions. Please use the '-a' switch to see them.", versions), versions); + } + + return true; +} + diff --git a/apt-private/private-list.h b/apt-private/private-list.h new file mode 100644 index 0000000..aa26777 --- /dev/null +++ b/apt-private/private-list.h @@ -0,0 +1,11 @@ +#ifndef APT_PRIVATE_LIST_H +#define APT_PRIVATE_LIST_H + +#include <apt-pkg/macros.h> + +class CommandLine; + +APT_PUBLIC bool DoList(CommandLine &Cmd); + + +#endif diff --git a/apt-private/private-main.cc b/apt-private/private-main.cc new file mode 100644 index 0000000..d4ce0ab --- /dev/null +++ b/apt-private/private-main.cc @@ -0,0 +1,90 @@ +#include <config.h> + +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/strutl.h> + +#include <apt-private/private-main.h> + +#include <iostream> +#include <locale> + +#include <signal.h> +#include <string.h> +#include <unistd.h> + +#include <apti18n.h> + +void InitLocale(APT_CMD const binary) /*{{{*/ +{ + try { + std::locale::global(std::locale("")); + } catch (...) { + setlocale(LC_ALL, ""); + } + switch(binary) + { + case APT_CMD::APT: + case APT_CMD::APT_CACHE: + case APT_CMD::APT_CDROM: + case APT_CMD::APT_CONFIG: + case APT_CMD::APT_DUMP_SOLVER: + case APT_CMD::APT_HELPER: + case APT_CMD::APT_GET: + case APT_CMD::APT_MARK: + case APT_CMD::RRED: + textdomain("apt"); + break; + case APT_CMD::APT_EXTRACTTEMPLATES: + case APT_CMD::APT_FTPARCHIVE: + case APT_CMD::APT_INTERNAL_PLANNER: + case APT_CMD::APT_INTERNAL_SOLVER: + case APT_CMD::APT_SORTPKG: + textdomain("apt-utils"); + break; + } +} + /*}}}*/ +void InitSignals() /*{{{*/ +{ + signal(SIGPIPE,SIG_IGN); +} + /*}}}*/ +void CheckIfSimulateMode(CommandLine &CmdL) /*{{{*/ +{ + // disable locking in simulation, but show the message only for users + // as root hasn't the same problems like unreadable files which can heavily + // distort the simulation. + if (_config->FindB("APT::Get::Simulate") == true && + (CmdL.FileSize() == 0 || + (strcmp(CmdL.FileList[0], "source") != 0 && strcmp(CmdL.FileList[0], "download") != 0 && + strcmp(CmdL.FileList[0], "changelog") != 0))) + { + if (getuid() != 0 && _config->FindB("APT::Get::Show-User-Simulation-Note",true) == true) + // TRANSLATORS: placeholder is a binary name like apt or apt-get + ioprintf(std::cout, _("NOTE: This is only a simulation!\n" + " %s needs root privileges for real execution.\n" + " Keep also in mind that locking is deactivated,\n" + " so don't depend on the relevance to the real current situation!\n"), + _config->Find("Binary").c_str()); + _config->Set("Debug::NoLocking",true); + } +} + /*}}}*/ +void CheckIfCalledByScript(int argc, const char *argv[]) /*{{{*/ +{ + if (unlikely(argc < 1)) return; + + if(!isatty(STDOUT_FILENO) && + _config->FindB("Apt::Cmd::Disable-Script-Warning", false) == false) + { + std::cerr << std::endl + << "WARNING: " << flNotDir(argv[0]) << " " + << "does not have a stable CLI interface. " + << "Use with caution in scripts." + << std::endl + << std::endl; + } +} + /*}}}*/ diff --git a/apt-private/private-main.h b/apt-private/private-main.h new file mode 100644 index 0000000..4dcb271 --- /dev/null +++ b/apt-private/private-main.h @@ -0,0 +1,15 @@ +#ifndef APT_PRIVATE_MAIN_H +#define APT_PRIVATE_MAIN_H + +#include <apt-private/private-cmndline.h> + +#include <apt-pkg/macros.h> + +class CommandLine; + +void InitLocale(APT_CMD const binary); +APT_PUBLIC void InitSignals(); +APT_PUBLIC void CheckIfSimulateMode(CommandLine &CmdL); +APT_PUBLIC void CheckIfCalledByScript(int argc, const char *argv[]); + +#endif diff --git a/apt-private/private-moo.cc b/apt-private/private-moo.cc new file mode 100644 index 0000000..ca456ed --- /dev/null +++ b/apt-private/private-moo.cc @@ -0,0 +1,200 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Here be cows – but: Never ask, never tell + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/strutl.h> + +#include <apt-private/private-moo.h> +#include <apt-private/private-output.h> +#include <apt-private/private-utils.h> + +#include <iostream> +#include <sstream> +#include <string> +#include <stddef.h> +#include <string.h> +#include <time.h> + +#include <apti18n.h> + /*}}}*/ + +static std::string getMooLine(time_t const timenow) /*{{{*/ +{ + struct tm special; + localtime_r(&timenow, &special); + enum { NORMAL, PACKAGEMANAGER, APPRECIATION, AGITATION, AIRBORN } line; + if (special.tm_mon == 11 && special.tm_mday == 25) + line = PACKAGEMANAGER; + else if (special.tm_mon == 7 && special.tm_mday == 16) + line = APPRECIATION; + else if (special.tm_mon == 10 && special.tm_mday == 7) + line = AGITATION; + else if (special.tm_mon == 1 && special.tm_mday == 18) + line = AIRBORN; + else + line = NORMAL; + + bool const quiet = _config->FindI("quiet") >= 2; + std::ostringstream out; + if (quiet == false) + out << "...\""; + + switch(line) + { + case PACKAGEMANAGER: out << "Happy package management day!"; break; + case APPRECIATION: out << "Three moos for Debian!"; break; + case AGITATION: out << "Whoever needs milk, bows to the animal."; break; + case AIRBORN: out << "It's a Bird ... It's a Plane ... It's Super Cow!"; break; + default: out << "Have you mooed today?"; break; + } + + if (quiet == true) + out << std::endl; + else + out << "\"..." << std::endl; + + return out.str(); +} + /*}}}*/ +static bool printMooLine(time_t const timenow) /*{{{*/ +{ + std::cerr << getMooLine(timenow); + return true; +} + /*}}}*/ +static bool DoMoo1(time_t const timenow) /*{{{*/ +{ + // our trustworthy super cow since 2001 + if (_config->FindI("quiet") >= 2) + return printMooLine(timenow); + std::string const moo = getMooLine(timenow); + size_t const depth = moo.length()/4; + c1out << + OutputInDepth(depth, " ") << " (__) \n" << + OutputInDepth(depth, " ") << " (oo) \n" << + OutputInDepth(depth, " ") << " /------\\/ \n" << + OutputInDepth(depth, " ") << " / | || \n" << + OutputInDepth(depth, " ") << " * /\\---/\\ \n" << + OutputInDepth(depth, " ") << " ~~ ~~ \n" << + moo; + return true; +} + /*}}}*/ +static bool DoMoo2(time_t const timenow) /*{{{*/ +{ + // by Fernando Ribeiro in lp:56125 + if (_config->FindI("quiet") >= 2) + return printMooLine(timenow); + std::string const moo = getMooLine(timenow); + size_t const depth = moo.length()/4; + if (_config->FindB("APT::Moo::Color", false) == false) + c1out << + OutputInDepth(depth, " ") << " (__) \n" << + OutputInDepth(depth, " ") << " _______~(..)~ \n" << + OutputInDepth(depth, " ") << " ,----\\(oo) \n" << + OutputInDepth(depth, " ") << " /|____|,' \n" << + OutputInDepth(depth, " ") << " * /\"\\ /\\ \n" << + OutputInDepth(depth, " ") << " ~ ~ ~ ~ \n" << + moo; + else + { + c1out << + OutputInDepth(depth, " ") << " \033[1;97m(\033[0;33m__\033[1;97m)\033[0m\n" << + OutputInDepth(depth, " ") << " \033[31m_______\033[33m~(\033[1;34m..\033[0;33m)~\033[0m\n" << + OutputInDepth(depth, " ") << " \033[33m,----\033[31m\\\033[33m(\033[1;4;35moo\033[0;33m)\033[0m\n" << + OutputInDepth(depth, " ") << " \033[33m/|____|,'\033[0m\n" << + OutputInDepth(depth, " ") << " \033[1;5;97m*\033[0;33m /\\ /\\\033[0m\n" << + "\033[32m"; + for (size_t i = moo.length()/2; i > 1; --i) + c1out << "wW"; + + c1out << "w\033[0m\n" << moo; + } + + return true; +} + /*}}}*/ +static bool DoMoo3(time_t const timenow) /*{{{*/ +{ + // by Robert Millan in deb:134156 + if (_config->FindI("quiet") >= 2) + return printMooLine(timenow); + std::string const moo = getMooLine(timenow); + size_t const depth = moo.length()/16; + c1out << + OutputInDepth(depth, " ") << " \\_/ \n" << + OutputInDepth(depth, " ") << " m00h (__) -(_)- \n" << + OutputInDepth(depth, " ") << " \\ ~Oo~___ / \\\n" << + OutputInDepth(depth, " ") << " (..) |\\ \n" << + OutputInDepth(depth, "_") << "_________|_|_|__________" << + OutputInDepth((moo.length() - (depth + 27)), "_") << "\n" << moo; + return true; +} + /*}}}*/ +static bool DoMooApril() /*{{{*/ +{ + // by Christopher Allan Webber and proposed by Paul Tagliamonte + // in a "Community outreach": https://lists.debian.org/debian-devel/2013/04/msg00045.html + if (_config->FindI("quiet") >= 2) + { + std::cerr << "Have you smashed some milk today?" << std::endl; + return true; + } + c1out << + " _ _\n" + " (_\\___( \\,\n" + " )___ _ Have you smashed some milk today?\n" + " /( (_)-(_) /\n" + " ,---------' \\_\n" + " //( ',__,' \\ (' ')\n" + " // ) '----'\n" + " '' ; \\ .--. ,/\n" + " | )',_,'----( ;\n" + " ||| ''' '||\n"; + return true; +} + /*}}}*/ +bool DoMoo(CommandLine &CmdL) /*{{{*/ +{ + time_t const timenow = GetSecondsSinceEpoch(); + + struct tm april; + localtime_r(&timenow, &april); + if (april.tm_mday == 1 && april.tm_mon == 3) + return DoMooApril(); + + signed short SuperCow = 1; + if (CmdL.FileSize() != 0) + for (const char **Moo = CmdL.FileList + 1; *Moo != 0; Moo++) + if (strcasecmp(*Moo, "moo") == 0) + SuperCow++; + + // time is random enough for our purpose + if (SuperCow > 3) + { + if (april.tm_sec == 1) + SuperCow = 1 + (timenow % 4); + else + SuperCow = 1 + (timenow % 3); + } + + switch(SuperCow) { + case 1: return DoMoo1(timenow); + case 2: return DoMoo2(timenow); + case 3: return DoMoo3(timenow); + case 4: return DoMooApril(); + default: return DoMoo1(timenow); + } + + return true; +} + /*}}}*/ diff --git a/apt-private/private-moo.h b/apt-private/private-moo.h new file mode 100644 index 0000000..c230ce2 --- /dev/null +++ b/apt-private/private-moo.h @@ -0,0 +1,10 @@ +#ifndef APT_PRIVATE_MOO_H +#define APT_PRIVATE_MOO_H + +#include <apt-pkg/macros.h> + +class CommandLine; + +APT_PUBLIC bool DoMoo(CommandLine &CmdL); + +#endif diff --git a/apt-private/private-output.cc b/apt-private/private-output.cc new file mode 100644 index 0000000..03fbe47 --- /dev/null +++ b/apt-private/private-output.cc @@ -0,0 +1,784 @@ +// Include files /*{{{*/ +#include <config.h> + +#include <apt-pkg/cachefile.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/error.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/policy.h> +#include <apt-pkg/strutl.h> + +#include <apt-private/private-cachefile.h> +#include <apt-private/private-output.h> + +#include <iomanip> +#include <iostream> +#include <langinfo.h> +#include <regex.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include <sstream> + +#include <apti18n.h> + /*}}}*/ + +using namespace std; + +std::ostream c0out(0); +std::ostream c1out(0); +std::ostream c2out(0); +std::ofstream devnull("/dev/null"); + + +unsigned int ScreenWidth = 80 - 1; /* - 1 for the cursor */ + +// SigWinch - Window size change signal handler /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static void SigWinch(int) +{ + // Riped from GNU ls +#ifdef TIOCGWINSZ + struct winsize ws; + + if (ioctl(1, TIOCGWINSZ, &ws) != -1 && ws.ws_col >= 5) + ScreenWidth = ws.ws_col - 1; +#endif +} + /*}}}*/ +bool InitOutput(std::basic_streambuf<char> * const out) /*{{{*/ +{ + if (!isatty(STDOUT_FILENO) && _config->FindI("quiet", -1) == -1) + _config->Set("quiet","1"); + + c0out.rdbuf(out); + c1out.rdbuf(out); + c2out.rdbuf(out); + if (_config->FindI("quiet",0) > 0) + c0out.rdbuf(devnull.rdbuf()); + if (_config->FindI("quiet",0) > 1) + c1out.rdbuf(devnull.rdbuf()); + + // deal with window size changes + auto cols = getenv("COLUMNS"); + if (cols != nullptr) + { + char * colends; + auto const sw = strtoul(cols, &colends, 10); + if (*colends != '\0' || sw == 0) + { + _error->Warning("Environment variable COLUMNS was ignored as it has an invalid value: \"%s\"", cols); + cols = nullptr; + } + else + ScreenWidth = sw; + } + if (cols == nullptr) + { + signal(SIGWINCH,SigWinch); + SigWinch(0); + } + + if (isatty(STDOUT_FILENO) == 0 || not _config->FindB("APT::Color", true) || getenv("NO_COLOR") != nullptr) + { + _config->Set("APT::Color", false); + _config->Set("APT::Color::Highlight", ""); + _config->Set("APT::Color::Neutral", ""); + } else { + // Colors + _config->CndSet("APT::Color::Highlight", "\x1B[32m"); + _config->CndSet("APT::Color::Neutral", "\x1B[0m"); + + _config->CndSet("APT::Color::Red", "\x1B[31m"); + _config->CndSet("APT::Color::Green", "\x1B[32m"); + _config->CndSet("APT::Color::Yellow", "\x1B[33m"); + _config->CndSet("APT::Color::Blue", "\x1B[34m"); + _config->CndSet("APT::Color::Magenta", "\x1B[35m"); + _config->CndSet("APT::Color::Cyan", "\x1B[36m"); + _config->CndSet("APT::Color::White", "\x1B[37m"); + } + + return true; +} + /*}}}*/ +static std::string GetArchiveSuite(pkgCacheFile &/*CacheFile*/, pkgCache::VerIterator ver) /*{{{*/ +{ + std::string suite = ""; + if (ver && ver.FileList()) + { + pkgCache::VerFileIterator VF = ver.FileList(); + for (; VF.end() == false ; ++VF) + { + if(VF.File() == NULL || VF.File().Archive() == NULL) + suite = suite + "," + _("unknown"); + else + suite = suite + "," + VF.File().Archive(); + //suite = VF.File().Archive(); + } + suite = suite.erase(0, 1); + } + return suite; +} + /*}}}*/ +static std::string GetFlagsStr(pkgCacheFile &CacheFile, pkgCache::PkgIterator P)/*{{{*/ +{ + pkgDepCache *DepCache = CacheFile.GetDepCache(); + pkgDepCache::StateCache &state = (*DepCache)[P]; + + std::string flags_str; + if (state.NowBroken()) + flags_str = "B"; + if (P.CurrentVer() && state.Upgradable() && state.CandidateVer != NULL) + flags_str = "g"; + else if (P.CurrentVer() != NULL) + flags_str = "i"; + else + flags_str = "-"; + return flags_str; +} + /*}}}*/ +static std::string GetCandidateVersion(pkgCacheFile &CacheFile, pkgCache::PkgIterator P)/*{{{*/ +{ + pkgPolicy *policy = CacheFile.GetPolicy(); + pkgCache::VerIterator cand = policy->GetCandidateVer(P); + + return cand ? cand.VerStr() : "(none)"; +} + /*}}}*/ +static std::string GetInstalledVersion(pkgCacheFile &/*CacheFile*/, pkgCache::PkgIterator P)/*{{{*/ +{ + pkgCache::VerIterator inst = P.CurrentVer(); + + return inst ? inst.VerStr() : "(none)"; +} + /*}}}*/ +static std::string GetVersion(pkgCacheFile &/*CacheFile*/, pkgCache::VerIterator V)/*{{{*/ +{ + pkgCache::PkgIterator P = V.ParentPkg(); + if (V == P.CurrentVer()) + { + std::string inst_str = DeNull(V.VerStr()); +#if 0 // FIXME: do we want this or something like this? + pkgDepCache *DepCache = CacheFile.GetDepCache(); + pkgDepCache::StateCache &state = (*DepCache)[P]; + if (state.Upgradable()) + return "**"+inst_str; +#endif + return inst_str; + } + + if(V) + return DeNull(V.VerStr()); + return "(none)"; +} + /*}}}*/ +static std::string GetArchitecture(pkgCacheFile &CacheFile, pkgCache::PkgIterator P)/*{{{*/ +{ + if (P->CurrentVer == 0) + { + pkgDepCache * const DepCache = CacheFile.GetDepCache(); + pkgDepCache::StateCache const &state = (*DepCache)[P]; + if (state.CandidateVer != NULL) + { + pkgCache::VerIterator const CandV(CacheFile, state.CandidateVer); + return CandV.Arch(); + } + else + { + pkgCache::VerIterator const V = P.VersionList(); + if (V.end() == false) + return V.Arch(); + else + return P.Arch(); + } + } + else + return P.CurrentVer().Arch(); +} + /*}}}*/ +static std::string GetShortDescription(pkgCacheFile &CacheFile, pkgRecords &records, pkgCache::PkgIterator P)/*{{{*/ +{ + pkgPolicy *policy = CacheFile.GetPolicy(); + + pkgCache::VerIterator ver; + if (P.CurrentVer()) + ver = P.CurrentVer(); + else + ver = policy->GetCandidateVer(P); + + std::string ShortDescription = "(none)"; + if(ver) + { + pkgCache::DescIterator const Desc = ver.TranslatedDescription(); + if (Desc.end() == false) + { + pkgRecords::Parser & parser = records.Lookup(Desc.FileList()); + ShortDescription = parser.ShortDesc(); + } + } + return ShortDescription; +} + /*}}}*/ +static std::string GetLongDescription(pkgCacheFile &CacheFile, pkgRecords &records, pkgCache::PkgIterator P)/*{{{*/ +{ + pkgPolicy *policy = CacheFile.GetPolicy(); + + pkgCache::VerIterator ver; + if (P->CurrentVer != 0) + ver = P.CurrentVer(); + else + ver = policy->GetCandidateVer(P); + + std::string const EmptyDescription = "(none)"; + if(ver.end() == true) + return EmptyDescription; + + pkgCache::DescIterator const Desc = ver.TranslatedDescription(); + if (Desc.end() == false) + { + pkgRecords::Parser & parser = records.Lookup(Desc.FileList()); + std::string const longdesc = parser.LongDesc(); + if (longdesc.empty() == false) + return SubstVar(longdesc, "\n ", "\n "); + } + return EmptyDescription; +} + /*}}}*/ +void ListSingleVersion(pkgCacheFile &CacheFile, pkgRecords &records, /*{{{*/ + pkgCache::VerIterator const &V, std::ostream &out, + std::string const &format) +{ + pkgCache::PkgIterator const P = V.ParentPkg(); + pkgDepCache * const DepCache = CacheFile.GetDepCache(); + pkgDepCache::StateCache const &state = (*DepCache)[P]; + + std::string output; + if (_config->FindB("APT::Cmd::use-format", false)) + output = _config->Find("APT::Cmd::format", "${db::Status-Abbrev} ${Package} ${Version} ${Origin} ${Description}"); + else + output = format; + + // FIXME: some of these names are really icky – and all is nowhere documented + output = SubstVar(output, "${db::Status-Abbrev}", GetFlagsStr(CacheFile, P)); + output = SubstVar(output, "${Package}", P.Name()); + std::string const ArchStr = GetArchitecture(CacheFile, P); + output = SubstVar(output, "${Architecture}", ArchStr); + std::string const InstalledVerStr = GetInstalledVersion(CacheFile, P); + output = SubstVar(output, "${installed:Version}", InstalledVerStr); + std::string const CandidateVerStr = GetCandidateVersion(CacheFile, P); + output = SubstVar(output, "${candidate:Version}", CandidateVerStr); + std::string const VersionStr = GetVersion(CacheFile, V); + output = SubstVar(output, "${Version}", VersionStr); + output = SubstVar(output, "${Origin}", GetArchiveSuite(CacheFile, V)); + + std::string StatusStr = ""; + if (P->CurrentVer != 0) + { + if (P.CurrentVer() == V) + { + if (state.Upgradable() && state.CandidateVer != NULL) + strprintf(StatusStr, _("[installed,upgradable to: %s]"), + CandidateVerStr.c_str()); + else if (V.Downloadable() == false) + StatusStr = _("[installed,local]"); + else if(V.Automatic() == true && state.Garbage == true) + StatusStr = _("[installed,auto-removable]"); + else if ((state.Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) + StatusStr = _("[installed,automatic]"); + else + StatusStr = _("[installed]"); + } + else if (state.CandidateVer == V && state.Upgradable()) + strprintf(StatusStr, _("[upgradable from: %s]"), + InstalledVerStr.c_str()); + } + else if (V.ParentPkg()->CurrentState == pkgCache::State::ConfigFiles) + StatusStr = _("[residual-config]"); + output = SubstVar(output, "${apt:Status}", StatusStr); + output = SubstVar(output, "${color:highlight}", _config->Find("APT::Color::Highlight", "")); + output = SubstVar(output, "${color:neutral}", _config->Find("APT::Color::Neutral", "")); + output = SubstVar(output, "${Description}", GetShortDescription(CacheFile, records, P)); + if (output.find("${LongDescription}") != string::npos) + output = SubstVar(output, "${LongDescription}", GetLongDescription(CacheFile, records, P)); + output = SubstVar(output, "${ }${ }", "${ }"); + output = SubstVar(output, "${ }\n", "\n"); + output = SubstVar(output, "${ }", " "); + if (APT::String::Endswith(output, " ") == true) + output.erase(output.length() - 1); + + out << output; +} + /*}}}*/ +// ShowBroken - Debugging aide /*{{{*/ +// --------------------------------------------------------------------- +/* This prints out the names of all the packages that are broken along + with the name of each broken dependency and a quite version + description. + + The output looks like: + The following packages have unmet dependencies: + exim: Depends: libc6 (>= 2.1.94) but 2.1.3-10 is to be installed + Depends: libldap2 (>= 2.0.2-2) but it is not going to be installed + Depends: libsasl7 but it is not going to be installed + */ +static void ShowBrokenPackage(ostream &out, pkgCacheFile * const Cache, pkgCache::PkgIterator const &Pkg, bool const Now) +{ + if (Now == true) + { + if ((*Cache)[Pkg].NowBroken() == false) + return; + } + else + { + if ((*Cache)[Pkg].InstBroken() == false) + return; + } + + // Print out each package and the failed dependencies + out << " " << Pkg.FullName(true) << " :"; + unsigned const Indent = Pkg.FullName(true).size() + 3; + bool First = true; + pkgCache::VerIterator Ver; + + if (Now == true) + Ver = Pkg.CurrentVer(); + else + Ver = (*Cache)[Pkg].InstVerIter(*Cache); + + if (Ver.end() == true) + { + out << endl; + return; + } + + for (pkgCache::DepIterator D = Ver.DependsList(); D.end() == false;) + { + // Compute a single dependency element (glob or) + pkgCache::DepIterator Start; + pkgCache::DepIterator End; + D.GlobOr(Start,End); // advances D + + if ((*Cache)->IsImportantDep(End) == false) + continue; + + if (Now == true) + { + if (((*Cache)[End] & pkgDepCache::DepGNow) == pkgDepCache::DepGNow) + continue; + } + else + { + if (((*Cache)[End] & pkgDepCache::DepGInstall) == pkgDepCache::DepGInstall) + continue; + } + + bool FirstOr = true; + while (1) + { + if (First == false) + for (unsigned J = 0; J != Indent; J++) + out << ' '; + First = false; + + if (FirstOr == false) + { + for (unsigned J = 0; J != strlen(End.DepType()) + 3; J++) + out << ' '; + } + else + out << ' ' << End.DepType() << ": "; + FirstOr = false; + + out << Start.TargetPkg().FullName(true); + + // Show a quick summary of the version requirements + if (Start.TargetVer() != 0) + out << " (" << Start.CompType() << " " << Start.TargetVer() << ")"; + + /* Show a summary of the target package if possible. In the case + of virtual packages we show nothing */ + pkgCache::PkgIterator Targ = Start.TargetPkg(); + if (Targ->ProvidesList == 0) + { + out << ' '; + pkgCache::VerIterator Ver = (*Cache)[Targ].InstVerIter(*Cache); + if (Now == true) + Ver = Targ.CurrentVer(); + + if (Ver.end() == false) + { + if (Now == true) + ioprintf(out,_("but %s is installed"),Ver.VerStr()); + else + ioprintf(out,_("but %s is to be installed"),Ver.VerStr()); + } + else + { + if ((*Cache)[Targ].CandidateVerIter(*Cache).end() == true) + { + if (Targ->ProvidesList == 0) + out << _("but it is not installable"); + else + out << _("but it is a virtual package"); + } + else + out << (Now?_("but it is not installed"):_("but it is not going to be installed")); + } + } + + if (Start != End) + out << _(" or"); + out << endl; + + if (Start == End) + break; + ++Start; + } + } +} +void ShowBroken(ostream &out, CacheFile &Cache, bool const Now) +{ + if (Cache->BrokenCount() == 0) + return; + + out << _("The following packages have unmet dependencies:") << endl; + SortedPackageUniverse Universe(Cache); + for (auto const &Pkg: Universe) + ShowBrokenPackage(out, &Cache, Pkg, Now); +} +void ShowBroken(ostream &out, pkgCacheFile &Cache, bool const Now) +{ + if (Cache->BrokenCount() == 0) + return; + + out << _("The following packages have unmet dependencies:") << endl; + APT::PackageUniverse Universe(Cache); + for (auto const &Pkg: Universe) + ShowBrokenPackage(out, &Cache, Pkg, Now); +} + /*}}}*/ +// ShowNew - Show packages to newly install /*{{{*/ +void ShowNew(ostream &out,CacheFile &Cache) +{ + SortedPackageUniverse Universe(Cache); + ShowList(out,_("The following NEW packages will be installed:"), Universe, + [&Cache](pkgCache::PkgIterator const &Pkg) { return Cache[Pkg].NewInstall(); }, + &PrettyFullName, + CandidateVersion(&Cache)); +} + /*}}}*/ +// ShowDel - Show packages to delete /*{{{*/ +void ShowDel(ostream &out,CacheFile &Cache) +{ + SortedPackageUniverse Universe(Cache); + ShowList(out,_("The following packages will be REMOVED:"), Universe, + [&Cache](pkgCache::PkgIterator const &Pkg) { return Cache[Pkg].Delete(); }, + [&Cache](pkgCache::PkgIterator const &Pkg) + { + std::string str = PrettyFullName(Pkg); + if (((*Cache)[Pkg].iFlags & pkgDepCache::Purge) == pkgDepCache::Purge) + str.append("*"); + return str; + }, + CandidateVersion(&Cache)); +} + /*}}}*/ +// ShowKept - Show kept packages /*{{{*/ +void ShowKept(ostream &out,CacheFile &Cache, APT::PackageVector const &HeldBackPackages) +{ + SortedPackageUniverse Universe(Cache); + ShowList(out,_("The following packages have been kept back:"), HeldBackPackages, + &AlwaysTrue, + &PrettyFullName, + CurrentToCandidateVersion(&Cache)); +} + /*}}}*/ +// ShowUpgraded - Show upgraded packages /*{{{*/ +void ShowUpgraded(ostream &out,CacheFile &Cache) +{ + SortedPackageUniverse Universe(Cache); + ShowList(out,_("The following packages will be upgraded:"), Universe, + [&Cache](pkgCache::PkgIterator const &Pkg) + { + return Cache[Pkg].Upgrade() == true && Cache[Pkg].NewInstall() == false; + }, + &PrettyFullName, + CurrentToCandidateVersion(&Cache)); +} + /*}}}*/ +// ShowDowngraded - Show downgraded packages /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool ShowDowngraded(ostream &out,CacheFile &Cache) +{ + SortedPackageUniverse Universe(Cache); + return ShowList(out,_("The following packages will be DOWNGRADED:"), Universe, + [&Cache](pkgCache::PkgIterator const &Pkg) + { + return Cache[Pkg].Downgrade() == true && Cache[Pkg].NewInstall() == false; + }, + &PrettyFullName, + CurrentToCandidateVersion(&Cache)); +} + /*}}}*/ +// ShowHold - Show held but changed packages /*{{{*/ +bool ShowHold(ostream &out,CacheFile &Cache) +{ + SortedPackageUniverse Universe(Cache); + return ShowList(out,_("The following held packages will be changed:"), Universe, + [&Cache](pkgCache::PkgIterator const &Pkg) + { + return Pkg->SelectedState == pkgCache::State::Hold && + Cache[Pkg].InstallVer != (pkgCache::Version *)Pkg.CurrentVer(); + }, + &PrettyFullName, + CurrentToCandidateVersion(&Cache)); +} + /*}}}*/ +// ShowEssential - Show an essential package warning /*{{{*/ +// --------------------------------------------------------------------- +/* This prints out a warning message that is not to be ignored. It shows + all essential packages and their dependents that are to be removed. + It is insanely risky to remove the dependents of an essential package! */ +struct APT_HIDDEN PrettyFullNameWithDue { + std::map<unsigned long long, pkgCache::PkgIterator> due; + PrettyFullNameWithDue() {} + std::string operator() (pkgCache::PkgIterator const &Pkg) + { + std::string const A = PrettyFullName(Pkg); + std::map<unsigned long long, pkgCache::PkgIterator>::const_iterator d = due.find(Pkg->ID); + if (d == due.end()) + return A; + + std::string const B = PrettyFullName(d->second); + std::ostringstream outstr; + ioprintf(outstr, _("%s (due to %s)"), A.c_str(), B.c_str()); + return outstr.str(); + } +}; +bool ShowEssential(ostream &out,CacheFile &Cache) +{ + std::vector<bool> Added(Cache->Head().PackageCount, false); + APT::PackageDeque pkglist; + PrettyFullNameWithDue withdue; + + SortedPackageUniverse Universe(Cache); + for (pkgCache::PkgIterator const &I: Universe) + { + if ((I->Flags & pkgCache::Flag::Essential) != pkgCache::Flag::Essential && + (I->Flags & pkgCache::Flag::Important) != pkgCache::Flag::Important) + continue; + + // The essential package is being removed + if (Cache[I].Delete() == false) + continue; + + if (Added[I->ID] == false) + { + Added[I->ID] = true; + pkglist.insert(I); + } + + if (I->CurrentVer == 0) + continue; + + // Print out any essential package depenendents that are to be removed + for (pkgCache::DepIterator D = I.CurrentVer().DependsList(); D.end() == false; ++D) + { + // Skip everything but depends + if (D->Type != pkgCache::Dep::PreDepends && + D->Type != pkgCache::Dep::Depends) + continue; + + pkgCache::PkgIterator P = D.SmartTargetPkg(); + if (Cache[P].Delete() == true) + { + if (Added[P->ID] == true) + continue; + Added[P->ID] = true; + + pkglist.insert(P); + withdue.due[P->ID] = I; + } + } + } + return ShowList(out,_("WARNING: The following essential packages will be removed.\n" + "This should NOT be done unless you know exactly what you are doing!"), + pkglist, &AlwaysTrue, withdue, &EmptyString); +} + /*}}}*/ +// Stats - Show some statistics /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void Stats(ostream &out, pkgDepCache &Dep, APT::PackageVector const &HeldBackPackages) +{ + unsigned long Upgrade = 0; + unsigned long Downgrade = 0; + unsigned long Install = 0; + unsigned long ReInstall = 0; + for (pkgCache::PkgIterator I = Dep.PkgBegin(); I.end() == false; ++I) + { + if (Dep[I].NewInstall() == true) + Install++; + else + { + if (Dep[I].Upgrade() == true) + Upgrade++; + else + if (Dep[I].Downgrade() == true) + Downgrade++; + } + + if (Dep[I].Delete() == false && (Dep[I].iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall) + ReInstall++; + } + + ioprintf(out,_("%lu upgraded, %lu newly installed, "), + Upgrade,Install); + + if (ReInstall != 0) + ioprintf(out,_("%lu reinstalled, "),ReInstall); + if (Downgrade != 0) + ioprintf(out,_("%lu downgraded, "),Downgrade); + + ioprintf(out,_("%lu to remove and %lu not upgraded.\n"), + Dep.DelCount(), HeldBackPackages.size()); + + if (Dep.BadCount() != 0) + ioprintf(out,_("%lu not fully installed or removed.\n"), + Dep.BadCount()); +} + /*}}}*/ +// YnPrompt - Yes No Prompt. /*{{{*/ +// --------------------------------------------------------------------- +/* Returns true on a Yes.*/ +bool YnPrompt(char const * const Question, bool const Default, bool const ShowGlobalErrors, std::ostream &c1o, std::ostream &c2o) +{ + auto const AssumeYes = _config->FindB("APT::Get::Assume-Yes",false); + auto const AssumeNo = _config->FindB("APT::Get::Assume-No",false); + // if we ask interactively, show warnings/notices before the question + if (ShowGlobalErrors == true && AssumeYes == false && AssumeNo == false) + { + if (_config->FindI("quiet",0) > 0) + _error->DumpErrors(c2o); + else + _error->DumpErrors(c2o, GlobalError::DEBUG); + } + + c2o << Question << std::flush; + + /* nl_langinfo does not support LANGUAGE setting, so we unset it here + to have the help-message (hopefully) match the expected characters */ + char * language = getenv("LANGUAGE"); + if (language != NULL) + language = strdup(language); + if (language != NULL) + unsetenv("LANGUAGE"); + + if (Default == true) + // TRANSLATOR: Yes/No question help-text: defaulting to Y[es] + // e.g. "Do you want to continue? [Y/n] " + // The user has to answer with an input matching the + // YESEXPR/NOEXPR defined in your l10n. + c2o << " " << _("[Y/n]") << " " << std::flush; + else + // TRANSLATOR: Yes/No question help-text: defaulting to N[o] + // e.g. "Should this file be removed? [y/N] " + // The user has to answer with an input matching the + // YESEXPR/NOEXPR defined in your l10n. + c2o << " " << _("[y/N]") << " " << std::flush; + + if (language != NULL) + { + setenv("LANGUAGE", language, 0); + free(language); + } + + if (AssumeYes) + { + // TRANSLATOR: "Yes" answer printed for a yes/no question if --assume-yes is set + c1o << _("Y") << std::endl; + return true; + } + else if (AssumeNo) + { + // TRANSLATOR: "No" answer printed for a yes/no question if --assume-no is set + c1o << _("N") << std::endl; + return false; + } + + char response[1024] = ""; + std::cin.getline(response, sizeof(response)); + + if (!std::cin) + return false; + + if (strlen(response) == 0) + return Default; + + regex_t Pattern; + int Res; + + Res = regcomp(&Pattern, nl_langinfo(YESEXPR), + REG_EXTENDED|REG_ICASE|REG_NOSUB); + + if (Res != 0) { + char Error[300]; + regerror(Res,&Pattern,Error,sizeof(Error)); + return _error->Error(_("Regex compilation error - %s"),Error); + } + + Res = regexec(&Pattern, response, 0, NULL, 0); + if (Res == 0) + return true; + return false; +} +bool YnPrompt(char const * const Question, bool const Default) +{ + return YnPrompt(Question, Default, true, c1out, c2out); +} + /*}}}*/ + +std::string PrettyFullName(pkgCache::PkgIterator const &Pkg) +{ + return Pkg.FullName(true); +} +std::string CandidateVersion(pkgCacheFile * const Cache, pkgCache::PkgIterator const &Pkg) +{ + return (*Cache)[Pkg].CandVersion; +} +std::function<std::string(pkgCache::PkgIterator const &)> CandidateVersion(pkgCacheFile * const Cache) +{ + return std::bind(static_cast<std::string(*)(pkgCacheFile * const, pkgCache::PkgIterator const&)>(&CandidateVersion), Cache, std::placeholders::_1); +} +std::string CurrentToCandidateVersion(pkgCacheFile * const Cache, pkgCache::PkgIterator const &Pkg) +{ + std::string const CurVer = (*Cache)[Pkg].CurVersion; + std::string CandVer = (*Cache)[Pkg].CandVersion; + if (CurVer == CandVer) + { + auto const CandVerIter = Cache->GetPolicy()->GetCandidateVer(Pkg); + if (not CandVerIter.end()) + CandVer = CandVerIter.VerStr(); + } + return CurVer + " => " + CandVer; +} +std::function<std::string(pkgCache::PkgIterator const &)> CurrentToCandidateVersion(pkgCacheFile * const Cache) +{ + return std::bind(static_cast<std::string(*)(pkgCacheFile * const, pkgCache::PkgIterator const&)>(&CurrentToCandidateVersion), Cache, std::placeholders::_1); +} +bool AlwaysTrue(pkgCache::PkgIterator const &) +{ + return true; +} +std::string EmptyString(pkgCache::PkgIterator const &) +{ + return std::string(); +} diff --git a/apt-private/private-output.h b/apt-private/private-output.h new file mode 100644 index 0000000..d6f25e1 --- /dev/null +++ b/apt-private/private-output.h @@ -0,0 +1,116 @@ +#ifndef APT_PRIVATE_OUTPUT_H +#define APT_PRIVATE_OUTPUT_H + +#include <apt-pkg/cacheset.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#include <fstream> +#include <functional> +#include <iostream> +#include <string> + +// forward declaration +class pkgCacheFile; +class CacheFile; +class pkgDepCache; +class pkgRecords; + + +APT_PUBLIC extern std::ostream c0out; +APT_PUBLIC extern std::ostream c1out; +APT_PUBLIC extern std::ostream c2out; +APT_PUBLIC extern std::ofstream devnull; +APT_PUBLIC extern unsigned int ScreenWidth; + +APT_PUBLIC bool InitOutput(std::basic_streambuf<char> * const out = std::cout.rdbuf()); + +void ListSingleVersion(pkgCacheFile &CacheFile, pkgRecords &records, + pkgCache::VerIterator const &V, std::ostream &out, + std::string const &format); + + +// helper to describe global state +APT_PUBLIC void ShowBroken(std::ostream &out, CacheFile &Cache, bool const Now); +APT_PUBLIC void ShowBroken(std::ostream &out, pkgCacheFile &Cache, bool const Now); + +template<class Container, class PredicateC, class DisplayP, class DisplayV> bool ShowList(std::ostream &out, std::string const &Title, + Container const &cont, + PredicateC Predicate, + DisplayP PkgDisplay, + DisplayV VerboseDisplay) +{ + size_t const ScreenWidth = (::ScreenWidth > 3) ? ::ScreenWidth - 3 : 0; + int ScreenUsed = 0; + bool const ShowVersions = _config->FindB("APT::Get::Show-Versions", false); + bool printedTitle = false; + + for (auto const &Pkg: cont) + { + if (Predicate(Pkg) == false) + continue; + + if (printedTitle == false) + { + out << Title; + printedTitle = true; + } + + if (ShowVersions == true) + { + out << std::endl << " " << PkgDisplay(Pkg); + std::string const verbose = VerboseDisplay(Pkg); + if (verbose.empty() == false) + out << " (" << verbose << ")"; + } + else + { + std::string const PkgName = PkgDisplay(Pkg); + if (ScreenUsed == 0 || (ScreenUsed + PkgName.length()) >= ScreenWidth) + { + out << std::endl << " "; + ScreenUsed = 0; + } + else if (ScreenUsed != 0) + { + out << " "; + ++ScreenUsed; + } + out << PkgName; + ScreenUsed += PkgName.length(); + } + } + + if (printedTitle == true) + { + out << std::endl; + return false; + } + return true; +} + +void ShowNew(std::ostream &out,CacheFile &Cache); +void ShowDel(std::ostream &out,CacheFile &Cache); +void ShowKept(std::ostream &out,CacheFile &Cache, APT::PackageVector const &HeldBackPackages); +void ShowUpgraded(std::ostream &out,CacheFile &Cache); +bool ShowDowngraded(std::ostream &out,CacheFile &Cache); +bool ShowHold(std::ostream &out,CacheFile &Cache); + +bool ShowEssential(std::ostream &out,CacheFile &Cache); + +void Stats(std::ostream &out, pkgDepCache &Dep, APT::PackageVector const &HeldBackPackages); + +// prompting +APT_PUBLIC bool YnPrompt(char const *const Question, bool Default = true); +bool YnPrompt(char const * const Question, bool const Default, bool const ShowGlobalErrors, std::ostream &c1o, std::ostream &c2o); + +APT_PUBLIC std::string PrettyFullName(pkgCache::PkgIterator const &Pkg); +std::string CandidateVersion(pkgCacheFile * const Cache, pkgCache::PkgIterator const &Pkg); +std::function<std::string(pkgCache::PkgIterator const &)> CandidateVersion(pkgCacheFile * const Cache); +std::string CurrentToCandidateVersion(pkgCacheFile * const Cache, pkgCache::PkgIterator const &Pkg); +std::function<std::string(pkgCache::PkgIterator const &)> CurrentToCandidateVersion(pkgCacheFile * const Cache); +std::string EmptyString(pkgCache::PkgIterator const &); +bool AlwaysTrue(pkgCache::PkgIterator const &); + +#endif diff --git a/apt-private/private-search.cc b/apt-private/private-search.cc new file mode 100644 index 0000000..b211474 --- /dev/null +++ b/apt-private/private-search.cc @@ -0,0 +1,418 @@ +// Includes /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/policy.h> +#include <apt-pkg/progress.h> + +#include <apt-private/private-cachefile.h> +#include <apt-private/private-cacheset.h> +#include <apt-private/private-json-hooks.h> +#include <apt-private/private-output.h> +#include <apt-private/private-search.h> +#include <apt-private/private-show.h> + +#include <iostream> +#include <map> +#include <sstream> +#include <string> +#include <utility> +#include <vector> +#include <string.h> + +#include <apti18n.h> + /*}}}*/ + +static std::vector<pkgCache::DescIterator> const TranslatedDescriptionsList(pkgCache::VerIterator const &V) /*{{{*/ +{ + std::vector<pkgCache::DescIterator> Descriptions; + + for (std::string const &lang: APT::Configuration::getLanguages()) + { + pkgCache::DescIterator Desc = V.TranslatedDescriptionForLanguage(lang); + if (Desc.IsGood()) + Descriptions.push_back(Desc); + } + + if (Descriptions.empty() && V.TranslatedDescription().IsGood()) + Descriptions.push_back(V.TranslatedDescription()); + + return Descriptions; +} + + /*}}}*/ +static bool FullTextSearch(CommandLine &CmdL) /*{{{*/ +{ + + CacheFile CacheFile; + CacheFile.GetDepCache(); + pkgCache *Cache = CacheFile.GetPkgCache(); + pkgDepCache::Policy *Plcy = CacheFile.GetPolicy(); + if (unlikely(Cache == NULL || Plcy == NULL)) + return false; + + // Make sure there is at least one argument + unsigned int const NumPatterns = CmdL.FileSize() -1; + if (NumPatterns < 1) + return _error->Error(_("You must give at least one search pattern")); + + RunJsonHook("AptCli::Hooks::Search", "org.debian.apt.hooks.search.pre", CmdL.FileList, CacheFile); + +#define APT_FREE_PATTERNS() for (std::vector<regex_t>::iterator P = Patterns.begin(); \ + P != Patterns.end(); ++P) { regfree(&(*P)); } + + // Compile the regex pattern + std::vector<regex_t> Patterns; + for (unsigned int I = 0; I != NumPatterns; ++I) + { + regex_t pattern; + if (regcomp(&pattern, CmdL.FileList[I + 1], REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0) + { + APT_FREE_PATTERNS(); + return _error->Error("Regex compilation error"); + } + Patterns.push_back(pattern); + } + + std::map<std::string, std::string> output_map; + + LocalitySortedVersionSet bag; + OpTextProgress progress(*_config); + progress.OverallProgress(0, 100, 50, _("Sorting")); + GetLocalitySortedVersionSet(CacheFile, &bag, &progress); + LocalitySortedVersionSet::iterator V = bag.begin(); + + progress.OverallProgress(50, 100, 50, _("Full Text Search")); + progress.SubProgress(bag.size()); + pkgRecords records(CacheFile); + + std::string format = "${color:highlight}${Package}${color:neutral}/${Origin} ${Version} ${Architecture}${ }${apt:Status}\n"; + if (_config->FindB("APT::Cache::ShowFull",false) == false) + format += " ${Description}\n"; + else + format += " ${LongDescription}\n"; + + bool const NamesOnly = _config->FindB("APT::Cache::NamesOnly", false); + int Done = 0; + std::vector<bool> PkgsDone(Cache->Head().PackageCount, false); + for ( ;V != bag.end(); ++V) + { + if (Done%500 == 0) + progress.Progress(Done); + ++Done; + + // we want to list each package only once + pkgCache::PkgIterator const P = V.ParentPkg(); + if (PkgsDone[P->ID] == true) + continue; + + std::vector<std::string> PkgDescriptions; + if (not NamesOnly) + { + for (auto &Desc: TranslatedDescriptionsList(V)) + { + pkgRecords::Parser &parser = records.Lookup(Desc.FileList()); + PkgDescriptions.push_back(parser.LongDesc()); + } + } + + bool all_found = true; + + char const * const PkgName = P.Name(); + std::vector<bool> SkipDescription(PkgDescriptions.size(), false); + for (std::vector<regex_t>::const_iterator pattern = Patterns.begin(); + pattern != Patterns.end(); ++pattern) + { + if (regexec(&(*pattern), PkgName, 0, 0, 0) == 0) + continue; + else if (not NamesOnly) + { + bool found = false; + + for (std::vector<std::string>::size_type i = 0; i < PkgDescriptions.size(); ++i) + { + if (not SkipDescription[i]) + { + if (regexec(&(*pattern), PkgDescriptions[i].c_str(), 0, 0, 0) == 0) + found = true; + else + SkipDescription[i] = true; + } + } + + if (found) + continue; + } + + // search patterns are AND, so one failing fails all + all_found = false; + break; + } + + if (all_found == true) + { + PkgsDone[P->ID] = true; + std::stringstream outs; + ListSingleVersion(CacheFile, records, V, outs, format); + output_map.insert(std::make_pair<std::string, std::string>( + PkgName, outs.str())); + } + } + APT_FREE_PATTERNS(); + progress.Done(); + + // FIXME: SORT! and make sorting flexible (alphabetic, by pkg status) + // output the sorted map + std::map<std::string, std::string>::const_iterator K; + for (K = output_map.begin(); K != output_map.end(); ++K) + std::cout << (*K).second << std::endl; + + if (output_map.empty()) + RunJsonHook("AptCli::Hooks::Search", "org.debian.apt.hooks.search.fail", CmdL.FileList, CacheFile); + else + RunJsonHook("AptCli::Hooks::Search", "org.debian.apt.hooks.search.post", CmdL.FileList, CacheFile); + return true; +} + /*}}}*/ +// LocalitySort - Sort a version list by package file locality /*{{{*/ +static int LocalityCompare(const void * const a, const void * const b) +{ + pkgCache::VerFile const * const A = *static_cast<pkgCache::VerFile const * const *>(a); + pkgCache::VerFile const * const B = *static_cast<pkgCache::VerFile const * const *>(b); + + if (A == 0 && B == 0) + return 0; + if (A == 0) + return 1; + if (B == 0) + return -1; + + if (A->File == B->File) + return A->Offset - B->Offset; + return A->File - B->File; +} +void LocalitySort(pkgCache::VerFile ** const begin, unsigned long long const Count,size_t const Size) +{ + qsort(begin,Count,Size,LocalityCompare); +} +static void LocalitySort(pkgCache::DescFile ** const begin, unsigned long long const Count,size_t const Size) +{ + qsort(begin,Count,Size,LocalityCompare); +} + /*}}}*/ +// Search - Perform a search /*{{{*/ +// --------------------------------------------------------------------- +/* This searches the package names and package descriptions for a pattern */ +struct ExDescFile +{ + pkgCache::DescFile *Df; + pkgCache::VerIterator V; + map_id_t ID; + ExDescFile() : Df(nullptr), ID(0) {} +}; +static bool Search(CommandLine &CmdL) +{ + bool const ShowFull = _config->FindB("APT::Cache::ShowFull",false); + unsigned int const NumPatterns = CmdL.FileSize() -1; + + pkgCacheFile CacheFile; + pkgCache *Cache = CacheFile.GetPkgCache(); + pkgDepCache::Policy *Plcy = CacheFile.GetPolicy(); + if (unlikely(Cache == NULL || Plcy == NULL)) + return false; + + // Make sure there is at least one argument + if (NumPatterns < 1) + return _error->Error(_("You must give at least one search pattern")); + + // Compile the regex pattern + regex_t *Patterns = new regex_t[NumPatterns]; + memset(Patterns,0,sizeof(*Patterns)*NumPatterns); + for (unsigned I = 0; I != NumPatterns; I++) + { + if (regcomp(&Patterns[I],CmdL.FileList[I+1],REG_EXTENDED | REG_ICASE | + REG_NOSUB) != 0) + { + for (; I != 0; I--) + regfree(&Patterns[I]); + return _error->Error("Regex compilation error"); + } + } + + if (_error->PendingError() == true) + { + for (unsigned I = 0; I != NumPatterns; I++) + regfree(&Patterns[I]); + return false; + } + + size_t const descCount = Cache->HeaderP->GroupCount + 1; + ExDescFile *DFList = new ExDescFile[descCount]; + + bool *PatternMatch = new bool[descCount * NumPatterns]; + memset(PatternMatch,false,sizeof(*PatternMatch) * descCount * NumPatterns); + + // Map versions that we want to write out onto the VerList array. + bool const NamesOnly = _config->FindB("APT::Cache::NamesOnly",false); + for (pkgCache::GrpIterator G = Cache->GrpBegin(); G.end() == false; ++G) + { + size_t const PatternOffset = G->ID * NumPatterns; + size_t unmatched = 0, matched = 0; + for (unsigned I = 0; I < NumPatterns; ++I) + { + if (PatternMatch[PatternOffset + I] == true) + ++matched; + else if (regexec(&Patterns[I],G.Name(),0,0,0) == 0) + PatternMatch[PatternOffset + I] = true; + else + ++unmatched; + } + + // already dealt with this package? + if (matched == NumPatterns) + continue; + + // Doing names only, drop any that don't match.. + if (NamesOnly == true && unmatched == NumPatterns) + continue; + + // Find the proper version to use + pkgCache::PkgIterator P = G.FindPreferredPkg(); + if (P.end() == true) + continue; + pkgCache::VerIterator V = Plcy->GetCandidateVer(P); + if (V.end() == false) + { + pkgCache::DescIterator const D = V.TranslatedDescription(); + //FIXME: packages without a description can't be found + if (D.end() == true) + continue; + DFList[G->ID].Df = D.FileList(); + DFList[G->ID].V = V; + DFList[G->ID].ID = G->ID; + } + + if (unmatched == NumPatterns) + continue; + + // Include all the packages that provide matching names too + for (pkgCache::PrvIterator Prv = P.ProvidesList() ; Prv.end() == false; ++Prv) + { + pkgCache::VerIterator V = Plcy->GetCandidateVer(Prv.OwnerPkg()); + if (V.end() == true) + continue; + + unsigned long id = Prv.OwnerPkg().Group()->ID; + pkgCache::DescIterator const D = V.TranslatedDescription(); + //FIXME: packages without a description can't be found + if (D.end() == true) + continue; + DFList[id].Df = D.FileList(); + DFList[id].V = V; + DFList[id].ID = id; + + size_t const PrvPatternOffset = id * NumPatterns; + for (unsigned I = 0; I < NumPatterns; ++I) + PatternMatch[PrvPatternOffset + I] |= PatternMatch[PatternOffset + I]; + } + } + + LocalitySort(&DFList->Df, Cache->HeaderP->GroupCount, sizeof(*DFList)); + + // Create the text record parser + pkgRecords Recs(*Cache); + // Iterate over all the version records and check them + for (ExDescFile *J = DFList; J->Df != 0; ++J) + { + size_t const PatternOffset = J->ID * NumPatterns; + if (not NamesOnly) + { + std::vector<std::string> PkgDescriptions; + for (auto &Desc: TranslatedDescriptionsList(J->V)) + { + pkgRecords::Parser &parser = Recs.Lookup(Desc.FileList()); + PkgDescriptions.push_back(parser.LongDesc()); + } + + std::vector<bool> SkipDescription(PkgDescriptions.size(), false); + for (unsigned I = 0; I < NumPatterns; ++I) + { + if (PatternMatch[PatternOffset + I]) + continue; + else + { + bool found = false; + + for (std::vector<std::string>::size_type k = 0; k < PkgDescriptions.size(); ++k) + { + if (not SkipDescription[k]) + { + if (regexec(&Patterns[I], PkgDescriptions[k].c_str(), 0, 0, 0) == 0) + { + found = true; + PatternMatch[PatternOffset + I] = true; + } + else + SkipDescription[k] = true; + } + } + + if (not found) + break; + } + } + } + + bool matchedAll = true; + for (unsigned I = 0; I < NumPatterns; ++I) + if (PatternMatch[PatternOffset + I] == false) + { + matchedAll = false; + break; + } + + if (matchedAll == true) + { + if (ShowFull == true) + { + pkgCache::VerFileIterator Vf; + auto &Parser = LookupParser(Recs, J->V, Vf); + char const *Start, *Stop; + Parser.GetRec(Start, Stop); + size_t const Length = Stop - Start; + DisplayRecordV1(CacheFile, Recs, J->V, Vf, Start, Length, std::cout); + } + else + { + pkgRecords::Parser &P = Recs.Lookup(pkgCache::DescFileIterator(*Cache, J->Df)); + printf("%s - %s\n", P.Name().c_str(), P.ShortDesc().c_str()); + } + } + } + + delete [] DFList; + delete [] PatternMatch; + for (unsigned I = 0; I != NumPatterns; I++) + regfree(&Patterns[I]); + delete [] Patterns; + if (ferror(stdout)) + return _error->Error("Write to stdout failed"); + return true; +} + /*}}}*/ +bool DoSearch(CommandLine &CmdL) /*{{{*/ +{ + int const ShowVersion = _config->FindI("APT::Cache::Search::Version", 1); + if (ShowVersion <= 1) + return Search(CmdL); + return FullTextSearch(CmdL); +} + diff --git a/apt-private/private-search.h b/apt-private/private-search.h new file mode 100644 index 0000000..518f297 --- /dev/null +++ b/apt-private/private-search.h @@ -0,0 +1,12 @@ +#ifndef APT_PRIVATE_SEARCH_H +#define APT_PRIVATE_SEARCH_H + +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +class CommandLine; + +APT_PUBLIC bool DoSearch(CommandLine &CmdL); +APT_PUBLIC void LocalitySort(pkgCache::VerFile ** const begin, unsigned long long const Count,size_t const Size); + +#endif diff --git a/apt-private/private-show.cc b/apt-private/private-show.cc new file mode 100644 index 0000000..08a4fe6 --- /dev/null +++ b/apt-private/private-show.cc @@ -0,0 +1,591 @@ +// Includes /*{{{*/ +#include <config.h> + +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/pkgsystem.h> +#include <apt-pkg/policy.h> +#include <apt-pkg/sourcelist.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile-keys.h> +#include <apt-pkg/tagfile.h> + +#include <apt-private/private-cacheset.h> +#include <apt-private/private-output.h> +#include <apt-private/private-install.h> +#include <apt-private/private-show.h> + +#include <ostream> +#include <string> +#include <stdio.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ + +pkgRecords::Parser &LookupParser(pkgRecords &Recs, pkgCache::VerIterator const &V, pkgCache::VerFileIterator &Vf) /*{{{*/ +{ + Vf = V.FileList(); + for (; Vf.end() == false; ++Vf) + if ((Vf.File()->Flags & pkgCache::Flag::NotSource) == 0) + break; + if (Vf.end() == true) + Vf = V.FileList(); + return Recs.Lookup(Vf); +} + /*}}}*/ +static APT_PURE char const *skipDescription(char const *DescP, size_t const Length, bool fields) /*{{{*/ +{ + auto const backup = DescP; + char const * const TagName = "\nDescription"; + size_t const TagLen = strlen(TagName); + while ((DescP = static_cast<char const *>(memchr(DescP, '\n', Length - (DescP - backup)))) != nullptr) + { + if (DescP[1] == ' ') + DescP += 2; + else if (fields && strncmp((char *)DescP, TagName, TagLen) == 0) + DescP += TagLen; + else + break; + } + if (DescP != NULL) + ++DescP; + return DescP; +} + /*}}}*/ +static APT_PURE char const *findDescriptionField(char const *DescP, size_t const Length) /*{{{*/ +{ + auto const backup = DescP; + char const * const TagName = "\nDescription"; + size_t const TagLen = strlen(TagName); + while ((DescP = static_cast<char const *>(memchr(DescP, '\n', Length - (DescP - backup)))) != nullptr) + { + if (strncmp(DescP, TagName, TagLen) == 0) + break; + else + ++DescP; + } + if (DescP != nullptr) + ++DescP; + return DescP; +} + /*}}}*/ +static APT_PURE char const *skipColonSpaces(char const *Buffer, size_t const Length) /*{{{*/ +{ + // skipping withspace before and after the field-value separating colon + char const *const Start = Buffer; + for (; isspace(*Buffer) != 0 && Length - (Buffer - Start) > 0; ++Buffer) + ; + if (*Buffer != ':') + return nullptr; + ++Buffer; + for (; isspace(*Buffer) != 0 && Length - (Buffer - Start) > 0; ++Buffer) + ; + if (Length < static_cast<size_t>(Buffer - Start)) + return nullptr; + return Buffer; +} + /*}}}*/ + +bool DisplayRecordV1(pkgCacheFile &, pkgRecords &Recs, /*{{{*/ + pkgCache::VerIterator const &V, pkgCache::VerFileIterator const &Vf, + char const *Buffer, size_t Length, std::ostream &out) +{ + if (unlikely(Length < 4)) + return false; + + auto const Desc = V.TranslatedDescription(); + if (Desc.end()) + { + /* This handles the unusual case that we have no description whatsoever. + The slightly more common case of only having a short-description embedded + in the record could be handled here, but apt supports also having multiple + descriptions embedded in the record, so we deal with that case later */ + if (FileFd::Write(STDOUT_FILENO, Buffer, Length) == false) + return false; + if (strncmp((Buffer + Length - 4), "\r\n\r\n", 4) != 0 && + strncmp((Buffer + Length - 2), "\n\n", 2) != 0) + out << std::endl; + return true; + } + + // Get a pointer to start of Description field + char const *DescP = findDescriptionField(Buffer, Length); + if (DescP == nullptr) + DescP = Buffer + Length; + + // Write all but Description + size_t const untilDesc = DescP - Buffer; + if (untilDesc != 0 && FileFd::Write(STDOUT_FILENO, Buffer, untilDesc) == false) + return false; + + // Show the right description + char desctag[50]; + auto const langcode = Desc.LanguageCode(); + if (strcmp(langcode, "") == 0) + strcpy(desctag, "\nDescription"); + else + snprintf(desctag, sizeof(desctag), "\nDescription-%s", langcode); + + out << desctag + 1 << ": " << std::flush; + auto const Df = Desc.FileList(); + if (Df.end() == false) + { + if (Desc.FileList()->File == Vf->File) + { + /* If we have the file already open look in the buffer for the + description we want to display. Note that this might not be the + only one we can encounter in this record */ + char const *Start = DescP; + do + { + if (strncmp(Start, desctag + 1, strlen(desctag) - 1) != 0) + continue; + Start += strlen(desctag) - 1; + Start = skipColonSpaces(Start, Length - (Start - Buffer)); + if (Start == nullptr) + continue; + char const *End = skipDescription(Start, Length - (Start - Buffer), false); + if (likely(End != nullptr)) + FileFd::Write(STDOUT_FILENO, Start, End - (Start + 1)); + break; + } while ((Start = findDescriptionField(Start, Length - (Start - Buffer))) != nullptr); + } + else + { + pkgRecords::Parser &P = Recs.Lookup(Df); + out << P.LongDesc(); + } + } + + out << std::endl << "Description-md5: " << Desc.md5() << std::endl; + + // Find the first field after the description (if there is any) + DescP = skipDescription(DescP, Length - (DescP - Buffer), true); + + // write the rest of the buffer, but skip mixed in Descriptions* fields + while (DescP != nullptr) + { + char const *const Start = DescP; + char const *End = findDescriptionField(DescP, Length - (DescP - Buffer)); + if (End == nullptr) + { + DescP = nullptr; + End = Buffer + Length - 1; + size_t endings = 0; + while (*End == '\n') + { + --End; + if (*End == '\r') + --End; + ++endings; + } + if (endings >= 1) + { + ++End; + if (*End == '\r') + ++End; + } + ++End; + } + else + DescP = skipDescription(End + strlen("Description"), Length - (End - Buffer), true); + + size_t const length = End - Start; + if (length != 0 && FileFd::Write(STDOUT_FILENO, Start, length) == false) + return false; + } + // write a final newline after the last field + out << std::endl; + + return true; +} + /*}}}*/ +static bool DisplayRecordV2(pkgCacheFile &CacheFile, pkgRecords &Recs, /*{{{*/ + pkgCache::VerIterator const &V, pkgCache::VerFileIterator const &Vf, + char const *Buffer, size_t const Length, std::ostream &out) +{ + // Check and load the package list file + pkgCache::PkgFileIterator I = Vf.File(); + + // find matching sources.list metaindex + pkgSourceList *SrcList = CacheFile.GetSourceList(); + pkgIndexFile *Index; + if (SrcList->FindIndex(I, Index) == false && + _system->FindIndex(I, Index) == false) + return _error->Error("Can not find indexfile for Package %s (%s)", + V.ParentPkg().Name(), V.VerStr()); + std::string source_index_file = Index->Describe(true); + + // Read the record + pkgTagSection Tags; + if (Tags.Scan(Buffer, Length, true) == false) + return _error->Error("Internal Error, Unable to parse a package record"); + + // make size nice + std::string installed_size; + auto const installed_size_field = Tags.FindULL(pkgTagSection::Key::Installed_Size); + if (installed_size_field > 0) + installed_size = SizeToStr(installed_size_field * 1024).append("B"); + else + installed_size = _("unknown"); + std::string package_size; + auto const package_size_field = Tags.FindULL(pkgTagSection::Key::Size); + if (package_size_field > 0) + package_size = SizeToStr(package_size_field).append("B"); + else + package_size = _("unknown"); + + const char *manual_installed = nullptr; + if (V.ParentPkg().CurrentVer() == V) + { + pkgDepCache *depCache = CacheFile.GetDepCache(); + if (unlikely(depCache == nullptr)) + return false; + pkgDepCache::StateCache &state = (*depCache)[V.ParentPkg()]; + manual_installed = !(state.Flags & pkgCache::Flag::Auto) ? "yes" : "no"; + } + + std::vector<pkgTagSection::Tag> RW; + // delete, apt-cache show has this info and most users do not care + if (not _config->FindB("APT::Cache::ShowFull", false)) + { + for (char const * const * type = HashString::SupportedHashes(); *type != nullptr; ++type) + RW.push_back(pkgTagSection::Tag::Remove(*type)); + RW.push_back(pkgTagSection::Tag::Remove("Filename")); + RW.push_back(pkgTagSection::Tag::Remove("Multi-Arch")); + RW.push_back(pkgTagSection::Tag::Remove("Conffiles")); + } + RW.push_back(pkgTagSection::Tag::Remove("Architecture")); + // we use the translated description + RW.push_back(pkgTagSection::Tag::Remove("Description")); + RW.push_back(pkgTagSection::Tag::Remove("Description-md5")); + // improve + RW.push_back(pkgTagSection::Tag::Rewrite("Package", V.ParentPkg().FullName(true))); + RW.push_back(pkgTagSection::Tag::Rewrite("Installed-Size", installed_size)); + RW.push_back(pkgTagSection::Tag::Remove("Size")); + RW.push_back(pkgTagSection::Tag::Rewrite("Download-Size", package_size)); + // add + if (manual_installed != nullptr) + RW.push_back(pkgTagSection::Tag::Rewrite("APT-Manual-Installed", manual_installed)); + RW.push_back(pkgTagSection::Tag::Rewrite("APT-Sources", source_index_file)); + + FileFd stdoutfd; + if (stdoutfd.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly, false) == false || + Tags.Write(stdoutfd, TFRewritePackageOrder, RW) == false || stdoutfd.Close() == false) + return _error->Error("Internal Error, Unable to parse a package record"); + + // write the description + // FIXME: show (optionally) all available translations(?) + pkgCache::DescIterator Desc = V.TranslatedDescription(); + if (Desc.end() == false) + { + pkgRecords::Parser &P = Recs.Lookup(Desc.FileList()); + out << "Description: " << P.LongDesc(); + } + + // write a final newline (after the description) + out << std::endl << std::endl; + + return true; +} + /*}}}*/ +bool ShowPackage(CommandLine &CmdL) /*{{{*/ +{ + pkgCacheFile CacheFile; + CacheFile.InhibitActionGroups(true); + auto VolatileCmdL = GetAllPackagesAsPseudo(CacheFile.GetSourceList(), CmdL, AddVolatileBinaryFile, ""); + + if (unlikely(CacheFile.GetPkgCache() == nullptr)) + return false; + CacheSetHelperVirtuals helper(true, GlobalError::NOTICE); + APT::CacheSetHelper::VerSelector const select = _config->FindB("APT::Cache::AllVersions", true) ? + APT::CacheSetHelper::ALL : APT::CacheSetHelper::CANDIDATE; + if (select == APT::CacheSetHelper::CANDIDATE && CacheFile.GetDepCache() == nullptr) + return false; + + APT::VersionList verset; + size_t normalPackages = 0; + for (auto const &I: VolatileCmdL) + { + if (I.index == -1) + { + APT::VersionContainerInterface::FromString(&verset, CacheFile, I.name, select, helper); + ++normalPackages; + } + else + { + if (select != APT::CacheSetHelper::CANDIDATE && unlikely(CacheFile.GetDepCache() == nullptr)) + return false; + pkgCache::PkgIterator const P = CacheFile->FindPkg(I.name); + if (unlikely(P.end())) + continue; + + // Set any version providing the .deb as the candidate. + for (auto Prv = P.ProvidesList(); Prv.end() == false; ++Prv) + { + if (I.release.empty()) + CacheFile->SetCandidateVersion(Prv.OwnerVer()); + else + CacheFile->SetCandidateRelease(Prv.OwnerVer(), I.release); + + // via cacheset to have our usual handling + APT::VersionContainerInterface::FromPackage(&verset, CacheFile, Prv.OwnerPkg(), APT::CacheSetHelper::CANDIDATE, helper); + } + } + } + + int const ShowVersion = _config->FindI("APT::Cache::Show::Version", 1); + pkgRecords Recs(CacheFile); + for (APT::VersionList::const_iterator Ver = verset.begin(); Ver != verset.end(); ++Ver) + { + pkgCache::VerFileIterator Vf; + auto &Parser = LookupParser(Recs, Ver, Vf); + char const *Start, *Stop; + Parser.GetRec(Start, Stop); + size_t const Length = Stop - Start; + + if (ShowVersion <= 1) + { + if (DisplayRecordV1(CacheFile, Recs, Ver, Vf, Start, Length, std::cout) == false) + return false; + } + else if (DisplayRecordV2(CacheFile, Recs, Ver, Vf, Start, Length + 1, c1out) == false) + return false; + } + + if (select == APT::CacheSetHelper::CANDIDATE && normalPackages != 0) + { + APT::VersionList verset_all; + for (auto const &I: VolatileCmdL) + { + if (I.index == -1) + APT::VersionContainerInterface::FromString(&verset_all, CacheFile, I.name, APT::CacheSetHelper::ALL, helper); + else + { + pkgCache::PkgIterator const P = CacheFile->FindPkg(I.name); + if (unlikely(P.end())) + continue; + + // Set any version providing the .deb as the candidate. + for (auto Prv = P.ProvidesList(); Prv.end() == false; ++Prv) + { + if (I.release.empty()) + CacheFile->SetCandidateVersion(Prv.OwnerVer()); + else + CacheFile->SetCandidateRelease(Prv.OwnerVer(), I.release); + + // via cacheset to have our usual virtual handling + APT::VersionContainerInterface::FromPackage(&verset_all, CacheFile, Prv.OwnerPkg(), APT::CacheSetHelper::CANDIDATE, helper); + } + } + } + + int const records = verset_all.size() - verset.size(); + if (records > 0) + _error->Notice(P_("There is %i additional record. Please use the '-a' switch to see it", "There are %i additional records. Please use the '-a' switch to see them.", records), records); + } + + if (_config->FindB("APT::Cache::ShowVirtuals", false) == true) + for (APT::PackageSet::const_iterator Pkg = helper.virtualPkgs.begin(); + Pkg != helper.virtualPkgs.end(); ++Pkg) + { + c1out << "Package: " << Pkg.FullName(true) << std::endl; + c1out << "State: " << _("not a real package (virtual)") << std::endl; + // FIXME: show providers, see private-cacheset.h + // CacheSetHelperAPTGet::showVirtualPackageErrors() + } + + if (verset.empty() == true) + { + if (helper.virtualPkgs.empty() == true) + return _error->Error(_("No packages found")); + else + _error->Notice(_("No packages found")); + } + + return true; +} + /*}}}*/ +static std::string Sha1FromString(std::string const &input) /*{{{*/ +{ + // XXX: move to hashes.h: HashString::FromString() ? + Hashes sha1(Hashes::SHA1SUM); + sha1.Add(input.c_str(), input.length()); + return sha1.GetHashString(Hashes::SHA1SUM).HashValue(); +} + /*}}}*/ +bool ShowSrcPackage(CommandLine &CmdL) /*{{{*/ +{ + pkgCacheFile CacheFile; + pkgSourceList *List = CacheFile.GetSourceList(); + if (unlikely(List == NULL)) + return false; + + // Create the text record parsers + pkgSrcRecords SrcRecs(*List); + if (_error->PendingError() == true) + return false; + + bool found = false; + // avoid showing identical records + std::set<std::string> seen; + for (const char **I = CmdL.FileList + 1; *I != 0; I++) + { + SrcRecs.Restart(); + + pkgSrcRecords::Parser *Parse; + bool found_this = false; + while ((Parse = SrcRecs.Find(*I,false)) != 0) { + // SrcRecs.Find() will find both binary and source names + if (_config->FindB("APT::Cache::Only-Source", false) == true) + if (Parse->Package() != *I) + continue; + std::string sha1str = Sha1FromString(Parse->AsStr()); + if (std::find(seen.begin(), seen.end(), sha1str) == seen.end()) + { + std::cout << Parse->AsStr() << std::endl;; + found = true; + found_this = true; + seen.insert(sha1str); + } + } + if (found_this == false) { + _error->Warning(_("Unable to locate package %s"),*I); + continue; + } + } + if (found == false) + _error->Notice(_("No packages found")); + return true; +} + /*}}}*/ +// Policy - Show the results of the preferences file /*{{{*/ +bool Policy(CommandLine &CmdL) +{ + pkgCacheFile CacheFile; + pkgSourceList const * const SrcList = CacheFile.GetSourceList(); + if (unlikely(SrcList == nullptr)) + return false; + pkgCache * const Cache = CacheFile.GetPkgCache(); + if (unlikely(Cache == nullptr)) + return false; + pkgPolicy * const Plcy = CacheFile.GetPolicy(); + if (unlikely(Plcy == nullptr)) + return false; + + // Print out all of the package files + if (CmdL.FileList[1] == 0) + { + std::cout << _("Package files:") << std::endl; + for (pkgCache::PkgFileIterator F = Cache->FileBegin(); F.end() == false; ++F) + { + if (F.Flagged(pkgCache::Flag::NoPackages)) + continue; + // Locate the associated index files so we can derive a description + pkgIndexFile *Indx; + if (SrcList->FindIndex(F,Indx) == false && + _system->FindIndex(F,Indx) == false) + return _error->Error(_("Cache is out of sync, can't x-ref a package file")); + + printf("%4i %s\n", + Plcy->GetPriority(F),Indx->Describe(true).c_str()); + + // Print the reference information for the package + std::string Str = F.RelStr(); + if (Str.empty() == false) + printf(" release %s\n",F.RelStr().c_str()); + if (F.Site() != 0 && F.Site()[0] != 0) + printf(" origin %s\n",F.Site()); + } + + // Show any packages have explicit pins + std::cout << _("Pinned packages:") << std::endl; + pkgCache::PkgIterator I = Cache->PkgBegin(); + for (;I.end() != true; ++I) + { + for (pkgCache::VerIterator V = I.VersionList(); !V.end(); ++V) { + auto Prio = Plcy->GetPriority(V, false); + if (Prio == 0) + continue; + + std::cout << " "; + // Print the package name and the version we are forcing to + ioprintf(std::cout, _("%s -> %s with priority %d\n"), I.FullName(true).c_str(), V.VerStr(), Prio); + } + } + return true; + } + + char const * const msgInstalled = _(" Installed: "); + char const * const msgCandidate = _(" Candidate: "); + short const InstalledLessCandidate = + mbstowcs(NULL, msgInstalled, 0) - mbstowcs(NULL, msgCandidate, 0); + short const deepInstalled = + (InstalledLessCandidate < 0 ? (InstalledLessCandidate*-1) : 0) - 1; + short const deepCandidate = + (InstalledLessCandidate > 0 ? (InstalledLessCandidate) : 0) - 1; + + // Print out detailed information for each package + APT::CacheSetHelper helper(true, GlobalError::NOTICE); + APT::PackageList pkgset = APT::PackageList::FromCommandLine(CacheFile, CmdL.FileList + 1, helper); + for (APT::PackageList::const_iterator Pkg = pkgset.begin(); Pkg != pkgset.end(); ++Pkg) + { + std::cout << Pkg.FullName(true) << ":" << std::endl; + + // Installed version + std::cout << msgInstalled << OutputInDepth(deepInstalled, " "); + if (Pkg->CurrentVer == 0) + std::cout << _("(none)") << std::endl; + else + std::cout << Pkg.CurrentVer().VerStr() << std::endl; + + // Candidate Version + std::cout << msgCandidate << OutputInDepth(deepCandidate, " "); + pkgCache::VerIterator V = Plcy->GetCandidateVer(Pkg); + if (V.end() == true) + std::cout << _("(none)") << std::endl; + else + std::cout << V.VerStr() << std::endl; + + // Show the priority tables + std::cout << _(" Version table:") << std::endl; + for (V = Pkg.VersionList(); V.end() == false; ++V) + { + if (Pkg.CurrentVer() == V) + std::cout << " *** " << V.VerStr(); + else + std::cout << " " << V.VerStr(); + + std::cout << " " << Plcy->GetPriority(V); + + if (V.PhasedUpdatePercentage() != 100) + std::cout << " " + << "(" << _("phased") << " " << V.PhasedUpdatePercentage() << "%)"; + + std::cout << std::endl; + for (pkgCache::VerFileIterator VF = V.FileList(); VF.end() == false; ++VF) + { + // Locate the associated index files so we can derive a description + pkgIndexFile *Indx; + if (SrcList->FindIndex(VF.File(),Indx) == false && + _system->FindIndex(VF.File(),Indx) == false) + return _error->Error(_("Cache is out of sync, can't x-ref a package file")); + printf(" %4i %s\n",Plcy->GetPriority(VF.File()), + Indx->Describe(true).c_str()); + } + } + } + return true; +} + /*}}}*/ diff --git a/apt-private/private-show.h b/apt-private/private-show.h new file mode 100644 index 0000000..9e5fa99 --- /dev/null +++ b/apt-private/private-show.h @@ -0,0 +1,22 @@ +#ifndef APT_PRIVATE_SHOW_H +#define APT_PRIVATE_SHOW_H + +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> + +#include <iostream> + +class CommandLine; +class pkgCacheFile; + +APT_PUBLIC bool ShowPackage(CommandLine &CmdL); +APT_PUBLIC bool ShowSrcPackage(CommandLine &CmdL); +APT_PUBLIC bool Policy(CommandLine &CmdL); + +pkgRecords::Parser &LookupParser(pkgRecords &Recs, pkgCache::VerIterator const &V, pkgCache::VerFileIterator &Vf); +bool DisplayRecordV1(pkgCacheFile &CacheFile, pkgRecords &Recs, + pkgCache::VerIterator const &V, pkgCache::VerFileIterator const &Vf, + char const *Buffer, size_t const Length, std::ostream &out); + +#endif diff --git a/apt-private/private-source.cc b/apt-private/private-source.cc new file mode 100644 index 0000000..14bc5b4 --- /dev/null +++ b/apt-private/private-source.cc @@ -0,0 +1,899 @@ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/acquire-item.h> +#include <apt-pkg/acquire.h> +#include <apt-pkg/algorithms.h> +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/metaindex.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/policy.h> +#include <apt-pkg/sourcelist.h> +#include <apt-pkg/srcrecords.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/version.h> + +#include <apt-private/private-cachefile.h> +#include <apt-private/private-cacheset.h> +#include <apt-private/private-download.h> +#include <apt-private/private-install.h> +#include <apt-private/private-source.h> + +#include <apt-pkg/debindexfile.h> +#include <apt-pkg/deblistparser.h> + +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <iostream> +#include <set> +#include <sstream> +#include <string> +#include <vector> + +#include <apti18n.h> + /*}}}*/ + +// GetReleaseFileForSourceRecord - Return Suite for the given srcrecord /*{{{*/ +static pkgCache::RlsFileIterator GetReleaseFileForSourceRecord(CacheFile &CacheFile, + pkgSourceList const * const SrcList, pkgSrcRecords::Parser const * const Parse) +{ + // try to find release + const pkgIndexFile& CurrentIndexFile = Parse->Index(); + + for (pkgSourceList::const_iterator S = SrcList->begin(); + S != SrcList->end(); ++S) + { + std::vector<pkgIndexFile *> *Indexes = (*S)->GetIndexFiles(); + for (std::vector<pkgIndexFile *>::const_iterator IF = Indexes->begin(); + IF != Indexes->end(); ++IF) + { + if (&CurrentIndexFile == (*IF)) + return (*S)->FindInCache(CacheFile, false); + } + } + return pkgCache::RlsFileIterator(CacheFile); +} + /*}}}*/ +// FindSrc - Find a source record /*{{{*/ +static pkgSrcRecords::Parser *FindSrc(const char *Name, + pkgSrcRecords &SrcRecs,std::string &Src, + CacheFile &Cache) +{ + std::string VerTag, UserRequestedVerTag; + std::string ArchTag = ""; + std::string RelTag = _config->Find("APT::Default-Release"); + std::string TmpSrc = Name; + + // extract release + size_t found = TmpSrc.find_last_of("/"); + if (found != std::string::npos) + { + RelTag = TmpSrc.substr(found+1); + TmpSrc = TmpSrc.substr(0,found); + } + // extract the version + found = TmpSrc.find_last_of("="); + if (found != std::string::npos) + { + VerTag = UserRequestedVerTag = TmpSrc.substr(found+1); + TmpSrc = TmpSrc.substr(0,found); + } + // extract arch + found = TmpSrc.find_last_of(":"); + if (found != std::string::npos) + { + ArchTag = TmpSrc.substr(found+1); + TmpSrc = TmpSrc.substr(0,found); + } + + /* Lookup the version of the package we would install if we were to + install a version and determine the source package name, then look + in the archive for a source package of the same name. */ + bool MatchSrcOnly = _config->FindB("APT::Get::Only-Source"); + pkgCache::PkgIterator Pkg; + if (ArchTag != "") + Pkg = Cache.GetPkgCache()->FindPkg(TmpSrc, ArchTag); + else + Pkg = Cache.GetPkgCache()->FindPkg(TmpSrc); + + // if we can't find a package but the user qualified with a arch, + // error out here + if (Pkg.end() && ArchTag != "") + { + Src = Name; + _error->Error(_("Can not find a package for architecture '%s'"), + ArchTag.c_str()); + return 0; + } + + if (MatchSrcOnly == false && Pkg.end() == false) + { + if(VerTag != "" || RelTag != "" || ArchTag != "") + { + bool fuzzy = false; + // we have a default release, try to locate the pkg. we do it like + // this because GetCandidateVer() will not "downgrade", that means + // "apt-get source -t stable apt" won't work on a unstable system + for (pkgCache::VerIterator Ver = Pkg.VersionList();; ++Ver) + { + // try first only exact matches, later fuzzy matches + if (Ver.end() == true) + { + if (fuzzy == true) + break; + fuzzy = true; + Ver = Pkg.VersionList(); + // exit right away from the Pkg.VersionList() loop if we + // don't have any versions + if (Ver.end() == true) + break; + } + + // ignore arches that are not for us + if (ArchTag != "" && Ver.Arch() != ArchTag) + continue; + + // pick highest version for the arch unless the user wants + // something else + if (ArchTag != "" && VerTag == "" && RelTag == "") + if(Cache.GetPkgCache()->VS->CmpVersion(VerTag, Ver.VerStr()) < 0) + VerTag = Ver.VerStr(); + + // We match against a concrete version (or a part of this version) + if (VerTag.empty() == false && + (fuzzy == true || Cache.GetPkgCache()->VS->CmpVersion(VerTag, Ver.VerStr()) != 0) && // exact match + (fuzzy == false || strncmp(VerTag.c_str(), Ver.VerStr(), VerTag.size()) != 0)) // fuzzy match + continue; + + for (pkgCache::VerFileIterator VF = Ver.FileList(); + VF.end() == false; ++VF) + { + /* If this is the status file, and the current version is not the + version in the status file (ie it is not installed, or somesuch) + then it is not a candidate for installation, ever. This weeds + out bogus entries that may be due to config-file states, or + other. */ + if ((VF.File()->Flags & pkgCache::Flag::NotSource) == + pkgCache::Flag::NotSource && Pkg.CurrentVer() != Ver) + continue; + + // or we match against a release + if(VerTag.empty() == false || + (VF.File().Archive() != 0 && VF.File().Archive() == RelTag) || + (VF.File().Codename() != 0 && VF.File().Codename() == RelTag)) + { + // the Version we have is possibly fuzzy or includes binUploads, + // so we use the Version of the SourcePkg (empty if same as package) + Src = Ver.SourcePkgName(); + VerTag = Ver.SourceVerStr(); + break; + } + } + if (Src.empty() == false) + break; + } + } + + if (Src.empty() == true && ArchTag.empty() == false) + { + if (VerTag.empty() == false) + _error->Error(_("Can not find a package '%s' with version '%s'"), + Pkg.FullName().c_str(), VerTag.c_str()); + if (RelTag.empty() == false) + _error->Error(_("Can not find a package '%s' with release '%s'"), + Pkg.FullName().c_str(), RelTag.c_str()); + Src = Name; + return 0; + } + + + if (Src.empty() == true) + { + // if we don't have found a fitting package yet so we will + // choose a good candidate and proceed with that. + // Maybe we will find a source later on with the right VerTag + // or RelTag + if (Cache.BuildPolicy() == false) + return nullptr; + pkgPolicy * const Policy = Cache.GetPolicy(); + pkgCache::VerIterator const Ver = Policy->GetCandidateVer(Pkg); + if (Ver.end() == false) + { + if (strcmp(Ver.SourcePkgName(),Ver.ParentPkg().Name()) != 0) + Src = Ver.SourcePkgName(); + if (VerTag.empty() == true && strcmp(Ver.SourceVerStr(),Ver.VerStr()) != 0) + VerTag = Ver.SourceVerStr(); + } + } + } + + if (Src.empty() == true) + { + Src = TmpSrc; + } + else + { + /* if we have a source pkg name, make sure to only search + for srcpkg names, otherwise apt gets confused if there + is a binary package "pkg1" and a source package "pkg1" + with the same name but that comes from different packages */ + MatchSrcOnly = true; + if (Src != TmpSrc) + { + ioprintf(c1out, _("Picking '%s' as source package instead of '%s'\n"), Src.c_str(), TmpSrc.c_str()); + } + } + + // The best hit + pkgSrcRecords::Parser *Last = 0; + unsigned long Offset = 0; + std::string Version; + pkgSourceList const * const SrcList = Cache.GetSourceList(); + pkgVersionMatch RelTagMatch{RelTag, pkgVersionMatch::Release}; + + /* Iterate over all of the hits, which includes the resulting + binary packages in the search */ + pkgSrcRecords::Parser *Parse; + while (true) + { + SrcRecs.Restart(); + while ((Parse = SrcRecs.Find(Src.c_str(), MatchSrcOnly)) != 0) + { + const std::string Ver = Parse->Version(); + + // See if we need to look for a specific release tag + if (RelTag.empty() == false && UserRequestedVerTag.empty() == true) + { + pkgCache::RlsFileIterator const Rls = GetReleaseFileForSourceRecord(Cache, SrcList, Parse); + if (not Rls.end() && not RelTagMatch.FileMatch(Rls)) + continue; + } + + // Ignore all versions which doesn't fit + if (VerTag.empty() == false && + Cache.GetPkgCache()->VS->CmpVersion(VerTag, Ver) != 0) // exact match + continue; + + // Newer version or an exact match? Save the hit + if (Last == 0 || Cache.GetPkgCache()->VS->CmpVersion(Version,Ver) < 0) { + Last = Parse; + Offset = Parse->Offset(); + Version = Ver; + } + + // was the version check above an exact match? + // If so, we don't need to look further + if (VerTag.empty() == false && (VerTag == Ver)) + break; + } + if (UserRequestedVerTag == "" && Version != "" && RelTag != "") + ioprintf(c1out, "Selected version '%s' (%s) for %s\n", + Version.c_str(), RelTag.c_str(), Src.c_str()); + + if (Last != 0 || VerTag.empty() == true) + break; + _error->Error(_("Can not find version '%s' of package '%s'"), VerTag.c_str(), TmpSrc.c_str()); + return 0; + } + + if (Last == 0 || Last->Jump(Offset) == false) + return 0; + + return Last; +} + /*}}}*/ +// DoSource - Fetch a source archive /*{{{*/ +// --------------------------------------------------------------------- +/* Fetch source packages */ +struct DscFile +{ + std::string Package; + std::string Version; + std::string Dsc; +}; +bool DoSource(CommandLine &CmdL) +{ + if (CmdL.FileSize() <= 1) + return _error->Error(_("Must specify at least one package to fetch source for")); + + CacheFile Cache; + if (Cache.BuildCaches(false) == false) + return false; + + // Create the text record parsers + pkgSourceList * const List = Cache.GetSourceList(); + pkgSrcRecords SrcRecs(*List); + if (_error->PendingError() == true) + return false; + + std::vector<DscFile> Dsc; + Dsc.reserve(CmdL.FileSize()); + + // insert all downloaded uris into this set to avoid downloading them + // twice + std::set<std::string> queued; + + // Diff only mode only fetches .diff files + bool const diffOnly = _config->FindB("APT::Get::Diff-Only", false); + // Tar only mode only fetches .tar files + bool const tarOnly = _config->FindB("APT::Get::Tar-Only", false); + // Dsc only mode only fetches .dsc files + bool const dscOnly = _config->FindB("APT::Get::Dsc-Only", false); + + // Load the requested sources into the fetcher + aptAcquireWithTextStatus Fetcher; + std::vector<std::string> UntrustedList; + for (const char **cmdl = CmdL.FileList + 1; *cmdl != 0; ++cmdl) + { + std::string Src; + pkgSrcRecords::Parser *Last = FindSrc(*cmdl, SrcRecs, Src, Cache); + if (Last == 0) { + return _error->Error(_("Unable to find a source package for %s"),Src.c_str()); + } + + if (Last->Index().IsTrusted() == false) + UntrustedList.push_back(Src); + + std::string srec = Last->AsStr(); + std::string::size_type pos = srec.find("\nVcs-"); + while (pos != std::string::npos) + { + pos += strlen("\nVcs-"); + std::string vcs = srec.substr(pos,srec.find(":",pos)-pos); + if(vcs == "Browser") + { + pos = srec.find("\nVcs-", pos); + continue; + } + pos += vcs.length()+2; + std::string::size_type epos = srec.find("\n", pos); + std::string const uri = srec.substr(pos,epos-pos); + ioprintf(c1out, _("NOTICE: '%s' packaging is maintained in " + "the '%s' version control system at:\n" + "%s\n"), + Src.c_str(), vcs.c_str(), uri.c_str()); + std::string vcscmd; + if (vcs == "Bzr") + vcscmd = "bzr branch " + uri; + else if (vcs == "Git") + vcscmd = "git clone " + uri; + + if (vcscmd.empty() == false) + ioprintf(c1out,_("Please use:\n%s\n" + "to retrieve the latest (possibly unreleased) " + "updates to the package.\n"), + vcscmd.c_str()); + break; + } + + // Back track + std::vector<pkgSrcRecords::File> Lst; + if (Last->Files(Lst) == false) { + return false; + } + + DscFile curDsc; + // Load them into the fetcher + for (std::vector<pkgSrcRecords::File>::const_iterator I = Lst.begin(); + I != Lst.end(); ++I) + { + // Try to guess what sort of file it is we are getting. + if (I->Type == "dsc") + { + curDsc.Package = Last->Package(); + curDsc.Version = Last->Version(); + curDsc.Dsc = flNotDir(I->Path); + } + + // Handle the only options so that multiple can be used at once + if (diffOnly == true || tarOnly == true || dscOnly == true) + { + if ((diffOnly == true && I->Type == "diff") || + (tarOnly == true && I->Type == "tar") || + (dscOnly == true && I->Type == "dsc")) + ; // Fine, we want this file downloaded + else + continue; + } + + // don't download the same uri twice (should this be moved to + // the fetcher interface itself?) + if(queued.find(Last->Index().ArchiveURI(I->Path)) != queued.end()) + continue; + queued.insert(Last->Index().ArchiveURI(I->Path)); + + // check if we have a file with that md5 sum already localy + std::string localFile = flNotDir(I->Path); + if (FileExists(localFile) == true) + if(I->Hashes.VerifyFile(localFile) == true) + { + ioprintf(c1out,_("Skipping already downloaded file '%s'\n"), + localFile.c_str()); + continue; + } + + // see if we have a hash (Acquire::ForceHash is the only way to have none) + if (I->Hashes.usable() == false && _config->FindB("APT::Get::AllowUnauthenticated",false) == false) + { + ioprintf(c1out, "Skipping download of file '%s' as requested hashsum is not available for authentication\n", + localFile.c_str()); + curDsc.Dsc.clear(); + continue; + } + + new pkgAcqFile(&Fetcher,Last->Index().ArchiveURI(I->Path), + I->Hashes, I->FileSize, Last->Index().SourceInfo(*Last,*I), Src); + } + Dsc.push_back(std::move(curDsc)); + } + + // Display statistics + unsigned long long FetchBytes = Fetcher.FetchNeeded(); + unsigned long long FetchPBytes = Fetcher.PartialPresent(); + unsigned long long DebBytes = Fetcher.TotalNeeded(); + + if (CheckFreeSpaceBeforeDownload(".", (FetchBytes - FetchPBytes)) == false) + return false; + + // Number of bytes + if (DebBytes != FetchBytes) + //TRANSLATOR: The required space between number and unit is already included + // in the replacement strings, so %sB will be correctly translate in e.g. 1,5 MB + ioprintf(c1out,_("Need to get %sB/%sB of source archives.\n"), + SizeToStr(FetchBytes).c_str(),SizeToStr(DebBytes).c_str()); + else + //TRANSLATOR: The required space between number and unit is already included + // in the replacement string, so %sB will be correctly translate in e.g. 1,5 MB + ioprintf(c1out,_("Need to get %sB of source archives.\n"), + SizeToStr(DebBytes).c_str()); + + if (_config->FindB("APT::Get::Simulate",false) == true) + { + for (auto const &D: Dsc) + ioprintf(std::cout, _("Fetch source %s\n"), D.Package.c_str()); + return true; + } + + // Just print out the uris an exit if the --print-uris flag was used + if (_config->FindB("APT::Get::Print-URIs") == true) + { + pkgAcquire::UriIterator I = Fetcher.UriBegin(); + for (; I != Fetcher.UriEnd(); ++I) + std::cout << '\'' << I->URI << "' " << flNotDir(I->Owner->DestFile) << ' ' << + std::to_string(I->Owner->FileSize) << ' ' << I->Owner->HashSum() << std::endl; + return true; + } + + // check authentication status of the source as well + if (UntrustedList.empty() == false && AuthPrompt(UntrustedList, false) == false) + return false; + + // Run it + bool Failed = false; + if (AcquireRun(Fetcher, 0, &Failed, NULL) == false || Failed == true) + return _error->Error(_("Failed to fetch some archives.")); + + if (diffOnly || tarOnly || dscOnly || _config->FindB("APT::Get::Download-only",false) == true) + { + c1out << _("Download complete and in download only mode") << std::endl; + return true; + } + + bool const fixBroken = _config->FindB("APT::Get::Fix-Broken", false); + bool SaidCheckIfDpkgDev = false; + for (auto const &D: Dsc) + { + if (unlikely(D.Dsc.empty() == true)) + continue; + std::string const Dir = D.Package + '-' + Cache.GetPkgCache()->VS->UpstreamVersion(D.Version.c_str()); + + // See if the package is already unpacked + struct stat Stat; + if (fixBroken == false && stat(Dir.c_str(),&Stat) == 0 && + S_ISDIR(Stat.st_mode) != 0) + { + ioprintf(c0out ,_("Skipping unpack of already unpacked source in %s\n"), + Dir.c_str()); + } + else + { + // Call dpkg-source + std::string const sourceopts = _config->Find("DPkg::Source-Options", "--no-check -x"); + std::string S; + strprintf(S, "%s %s %s", + _config->Find("Dir::Bin::dpkg-source","dpkg-source").c_str(), + sourceopts.c_str(), D.Dsc.c_str()); + if (system(S.c_str()) != 0) + { + _error->Error(_("Unpack command '%s' failed.\n"), S.c_str()); + if (SaidCheckIfDpkgDev == false) + { + _error->Notice(_("Check if the 'dpkg-dev' package is installed.\n")); + SaidCheckIfDpkgDev = true; + } + continue; + } + } + + // Try to compile it with dpkg-buildpackage + if (_config->FindB("APT::Get::Compile",false) == true) + { + std::string buildopts = _config->Find("APT::Get::Host-Architecture"); + if (buildopts.empty() == false) + buildopts = "-a" + buildopts + " "; + + // get all active build profiles + std::string const profiles = APT::Configuration::getBuildProfilesString(); + if (profiles.empty() == false) + buildopts.append(" -P").append(profiles).append(" "); + + buildopts.append(_config->Find("DPkg::Build-Options","-b -uc")); + + // Call dpkg-buildpackage + std::string S; + strprintf(S, "cd %s && %s %s", + Dir.c_str(), + _config->Find("Dir::Bin::dpkg-buildpackage","dpkg-buildpackage").c_str(), + buildopts.c_str()); + + if (system(S.c_str()) != 0) + { + _error->Error(_("Build command '%s' failed.\n"), S.c_str()); + continue; + } + } + } + return true; +} + /*}}}*/ +// DoBuildDep - Install/removes packages to satisfy build dependencies /*{{{*/ +// --------------------------------------------------------------------- +/* This function will look at the build depends list of the given source + package and install the necessary packages to make it true, or fail. */ +static std::vector<pkgSrcRecords::Parser::BuildDepRec> GetBuildDeps(pkgSrcRecords::Parser * const Last, + char const * const Src, std::string const &hostArch) +{ + std::vector<pkgSrcRecords::Parser::BuildDepRec> BuildDeps; + // FIXME: Can't specify architecture to use for [wildcard] matching, so switch default arch temporary + if (hostArch.empty() == false) + { + std::string nativeArch = _config->Find("APT::Architecture"); + _config->Set("APT::Architecture", hostArch); + bool Success = Last->BuildDepends(BuildDeps, _config->FindB("APT::Get::Arch-Only", false), false); + _config->Set("APT::Architecture", nativeArch); + if (Success == false) + { + _error->Error(_("Unable to get build-dependency information for %s"), Src); + return {}; + } + } + else if (Last->BuildDepends(BuildDeps, _config->FindB("APT::Get::Arch-Only", false), false) == false) + { + _error->Error(_("Unable to get build-dependency information for %s"), Src); + return {}; + } + + if (BuildDeps.empty() == true) + ioprintf(c1out,_("%s has no build depends.\n"), Src); + + return BuildDeps; +} +static void WriteBuildDependencyPackage(std::ostringstream &buildDepsPkgFile, + std::string const &PkgName, std::string const &Arch, + std::vector<pkgSrcRecords::Parser::BuildDepRec> const &Dependencies) +{ + buildDepsPkgFile << "Package: " << PkgName << "\n" + << "Architecture: " << Arch << "\n" + << "Version: 1\n"; + + bool const IndepOnly = _config->FindB("APT::Get::Indep-Only", false); + std::string depends, conflicts; + for (auto const &dep: Dependencies) + { + // ArchOnly is handled while parsing the dependencies on input + if (IndepOnly && (dep.Type == pkgSrcRecords::Parser::BuildDependArch || + dep.Type == pkgSrcRecords::Parser::BuildConflictArch)) + continue; + std::string * type; + if (dep.Type == pkgSrcRecords::Parser::BuildConflict || + dep.Type == pkgSrcRecords::Parser::BuildConflictIndep || + dep.Type == pkgSrcRecords::Parser::BuildConflictArch) + type = &conflicts; + else + type = &depends; + + type->append(" ").append(dep.Package); + if (dep.Version.empty() == false) + type->append(" (").append(pkgCache::CompTypeDeb(dep.Op)).append(" ").append(dep.Version).append(")"); + if ((dep.Op & pkgCache::Dep::Or) == pkgCache::Dep::Or) + { + type->append("\n |"); + } + else + type->append(",\n"); + } + if (depends.empty() == false) + buildDepsPkgFile << "Depends:\n" << depends; + if (conflicts.empty() == false) + buildDepsPkgFile << "Conflicts:\n" << conflicts; + buildDepsPkgFile << "\n"; +} +bool DoBuildDep(CommandLine &CmdL) +{ + std::string hostArch = _config->Find("APT::Get::Host-Architecture"); + if (not hostArch.empty()) + { + if (not APT::Configuration::checkArchitecture(hostArch)) + { + auto const veryforeign = _config->FindVector("APT::BarbarianArchitectures"); + if (std::find(veryforeign.begin(), veryforeign.end(), hostArch) == veryforeign.end()) + _error->Warning(_("No architecture information available for %s. See apt.conf(5) APT::Architectures for setup"), hostArch.c_str()); + } + } + auto const nativeArch = _config->Find("APT::Architecture"); + std::string const pseudoArch = hostArch.empty() ? nativeArch : hostArch; + + CacheFile Cache; + Cache.InhibitActionGroups(true); + auto VolatileCmdL = GetPseudoPackages(Cache.GetSourceList(), CmdL, AddVolatileSourceFile, pseudoArch); + auto AreDoingSatisfy = strcasecmp(CmdL.FileList[0], "satisfy") == 0; + + if (not AreDoingSatisfy) + _config->Set("APT::Install-Recommends", false); + + if (CmdL.FileSize() <= 1 && VolatileCmdL.empty()) + return _error->Error(_("Must specify at least one package to check builddeps for")); + + std::ostringstream buildDepsPkgFile; + std::vector<PseudoPkg> pseudoPkgs; + // deal with the build essentials first + if (not AreDoingSatisfy) + { + std::vector<pkgSrcRecords::Parser::BuildDepRec> BuildDeps; + for (auto && opt: _config->FindVector("APT::Build-Essential")) + { + if (opt.empty()) + continue; + pkgSrcRecords::Parser::BuildDepRec rec; + rec.Package = std::move(opt); + rec.Type = pkgSrcRecords::Parser::BuildDependIndep; + rec.Op = 0; + BuildDeps.push_back(rec); + } + std::string const pseudo = "builddeps:essentials"; + WriteBuildDependencyPackage(buildDepsPkgFile, pseudo, nativeArch, BuildDeps); + pseudoPkgs.emplace_back(pseudo, nativeArch, ""); + } + + if (AreDoingSatisfy) + { + std::vector<pkgSrcRecords::Parser::BuildDepRec> BuildDeps; + for (unsigned i = 1; i < CmdL.FileSize(); i++) + { + const char *Start = CmdL.FileList[i]; + const char *Stop = Start + strlen(Start); + auto Type = pkgSrcRecords::Parser::BuildDependIndep; + + // Reject '>' and '<' as operators, as they have strange meanings. + bool insideVersionRestriction = false; + for (auto C = Start; C + 1 < Stop; C++) + { + if (*C == '(') + insideVersionRestriction = true; + else if (*C == ')') + insideVersionRestriction = false; + else if (insideVersionRestriction && (*C == '<' || *C == '>')) + { + if (C[1] != *C && C[1] != '=') + return _error->Error(_("Invalid operator '%c' at offset %d, did you mean '%c%c' or '%c='? - in: %s"), *C, (int)(C - Start), *C, *C, *C, Start); + C++; + } + } + + if (APT::String::Startswith(Start, "Conflicts:")) + { + Type = pkgSrcRecords::Parser::BuildConflictIndep; + Start += strlen("Conflicts:"); + } + while (1) + { + pkgSrcRecords::Parser::BuildDepRec rec; + Start = debListParser::ParseDepends(Start, Stop, + rec.Package, rec.Version, rec.Op, true, false, true, pseudoArch); + + if (Start == 0) + return _error->Error("Problem parsing dependency: %s", CmdL.FileList[i]); + rec.Type = Type; + + // We parsed a package that was ignored (wrong architecture restriction + // or something). + if (rec.Package.empty()) + { + // If we are in an OR group, we need to set the "Or" flag of the + // previous entry to our value. + if (BuildDeps.empty() == false && (BuildDeps[BuildDeps.size() - 1].Op & pkgCache::Dep::Or) == pkgCache::Dep::Or) + { + BuildDeps[BuildDeps.size() - 1].Op &= ~pkgCache::Dep::Or; + BuildDeps[BuildDeps.size() - 1].Op |= (rec.Op & pkgCache::Dep::Or); + } + } + else + { + BuildDeps.emplace_back(std::move(rec)); + } + + if (Start == Stop) + break; + } + } + std::string const pseudo = "satisfy:command-line"; + WriteBuildDependencyPackage(buildDepsPkgFile, pseudo, pseudoArch, BuildDeps); + pseudoPkgs.emplace_back(pseudo, pseudoArch, ""); + } + + // Read the source list + if (Cache.BuildSourceList() == false) + return false; + pkgSourceList *List = Cache.GetSourceList(); + + if (not AreDoingSatisfy) + { + auto const VolatileSources = List->GetVolatileFiles(); + for (auto &&pkg : VolatileCmdL) + { + if (unlikely(pkg.index == -1)) + { + _error->Error(_("Unable to find a source package for %s"), pkg.name.c_str()); + continue; + } + if (DirectoryExists(pkg.name)) + ioprintf(c1out, _("Note, using directory '%s' to get the build dependencies\n"), pkg.name.c_str()); + else + ioprintf(c1out, _("Note, using file '%s' to get the build dependencies\n"), pkg.name.c_str()); + std::unique_ptr<pkgSrcRecords::Parser> Last(VolatileSources[pkg.index]->CreateSrcParser()); + if (Last == nullptr) + { + _error->Error(_("Unable to find a source package for %s"), pkg.name.c_str()); + continue; + } + + auto pseudo = std::string("builddeps:") + pkg.name; + WriteBuildDependencyPackage(buildDepsPkgFile, pseudo, pseudoArch, + GetBuildDeps(Last.get(), pkg.name.c_str(), hostArch)); + pkg.name = std::move(pseudo); + pseudoPkgs.push_back(std::move(pkg)); + } + VolatileCmdL.clear(); + } + + bool const WantLock = _config->FindB("APT::Get::Print-URIs", false) == false; + if (CmdL.FileList[1] != 0 && not AreDoingSatisfy) + { + if (Cache.BuildCaches(WantLock) == false) + return false; + // Create the text record parsers + pkgSrcRecords SrcRecs(*List); + if (_error->PendingError() == true) + return false; + for (const char **I = CmdL.FileList + 1; *I != 0; ++I) + { + std::string Src; + pkgSrcRecords::Parser * const Last = FindSrc(*I,SrcRecs,Src,Cache); + if (Last == nullptr) + return _error->Error(_("Unable to find a source package for %s"), *I); + + std::string const pseudo = std::string("builddeps:") + Src; + WriteBuildDependencyPackage(buildDepsPkgFile, pseudo, pseudoArch, + GetBuildDeps(Last, Src.c_str(), hostArch)); + std::string reltag = *I; + size_t found = reltag.find_last_of("/"); + if (found == std::string::npos) + reltag.clear(); + else + reltag.erase(0, found + 1); + pseudoPkgs.emplace_back(pseudo, pseudoArch, std::move(reltag)); + } + } + + Cache.AddIndexFile(new debStringPackageIndex(buildDepsPkgFile.str())); + + if (Cache.Open(WantLock) == false) + return false; + pkgProblemResolver Fix(Cache.GetDepCache()); + + APT::PackageSet UpgradablePackages; + APT::PackageVector removeAgain; + { + TryToInstall InstallAction(Cache, &Fix, false); + std::list<std::pair<pkgCache::VerIterator, std::string>> candSwitch; + for (auto const &pkg: pseudoPkgs) + { + pkgCache::PkgIterator const Pkg = Cache->FindPkg(pkg.name, pkg.arch); + if (Pkg.end()) + continue; + if (pkg.release.empty()) + Cache->SetCandidateVersion(Pkg.VersionList()); + else + candSwitch.emplace_back(Pkg.VersionList(), pkg.release); + } + if (candSwitch.empty() == false) + InstallAction.propagateReleaseCandidateSwitching(candSwitch, c0out); + for (auto const &pkg: pseudoPkgs) + { + pkgCache::PkgIterator const Pkg = Cache->FindPkg(pkg.name, pkg.arch); + if (Pkg.end()) + continue; + InstallAction(Cache[Pkg].CandidateVerIter(Cache)); + removeAgain.push_back(Pkg); + } + + { + APT::CacheSetHelper helper; + helper.PackageFrom(APT::CacheSetHelper::PATTERN, &UpgradablePackages, Cache, "?upgradable"); + } + InstallAction.doAutoInstall(); + + OpTextProgress Progress(*_config); + bool const resolver_fail = Fix.Resolve(true, &Progress); + if (resolver_fail == false && Cache->BrokenCount() == 0) + return false; + if (CheckNothingBroken(Cache) == false) + return false; + } + if (DoAutomaticRemove(Cache) == false) + return false; + + { + if (_config->FindB(AreDoingSatisfy ? "APT::Get::Satisfy-Automatic" : "APT::Get::Build-Dep-Automatic", false) == false) + { + for (auto const &pkg: removeAgain) + { + auto const instVer = Cache[pkg].InstVerIter(Cache); + if (unlikely(instVer.end() == true)) + continue; + for (auto D = instVer.DependsList(); D.end() != true; ++D) + { + if (D->Type != pkgCache::Dep::Depends || D.IsMultiArchImplicit()) + continue; + APT::VersionList verlist = APT::VersionList::FromDependency(Cache, D, APT::CacheSetHelper::CANDIDATE); + for (auto const &V : verlist) + { + auto const P = V.ParentPkg(); + if (Cache[P].InstallVer != V) + continue; + Cache->MarkAuto(P, false); + } + } + } + } + for (auto const &pkg: removeAgain) + Cache->MarkDelete(pkg, false, 0, true); + } + + APT::PackageVector HeldBackPackages; + SortedPackageUniverse Universe(Cache); + for (auto const &Pkg: Universe) + if (Pkg->CurrentVer != 0 && not Cache[Pkg].Upgrade() && not Cache[Pkg].Delete() && + UpgradablePackages.find(Pkg) != UpgradablePackages.end()) + HeldBackPackages.push_back(Pkg); + + pseudoPkgs.clear(); + if (_error->PendingError() || not InstallPackages(Cache, HeldBackPackages, false, true)) + return _error->Error(_("Failed to process build dependencies")); + return true; +} + /*}}}*/ diff --git a/apt-private/private-source.h b/apt-private/private-source.h new file mode 100644 index 0000000..d00c53e --- /dev/null +++ b/apt-private/private-source.h @@ -0,0 +1,11 @@ +#ifndef APT_PRIVATE_SOURCE_H +#define APT_PRIVATE_SOURCE_H + +#include <apt-pkg/macros.h> + +class CommandLine; + +APT_PUBLIC bool DoSource(CommandLine &CmdL); +APT_PUBLIC bool DoBuildDep(CommandLine &CmdL); + +#endif diff --git a/apt-private/private-sources.cc b/apt-private/private-sources.cc new file mode 100644 index 0000000..4cfb879 --- /dev/null +++ b/apt-private/private-sources.cc @@ -0,0 +1,105 @@ +#include <config.h> + +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/sourcelist.h> +#include <apt-pkg/strutl.h> + +#include <apt-private/private-output.h> +#include <apt-private/private-sources.h> +#include <apt-private/private-utils.h> + +#include <iostream> +#include <string> +#include <stddef.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <apti18n.h> + +/* Interface discussion with donkult (for the future): + apt [add-{archive,release,component}|edit|change-release|disable]-sources + and be clever and work out stuff from the Release file +*/ + +// EditSource - EditSourcesList /*{{{*/ +class APT_HIDDEN ScopedGetLock { +public: + int fd; + explicit ScopedGetLock(std::string const &filename) : fd(GetLock(filename)) {} + ~ScopedGetLock() { close(fd); } +}; +bool EditSources(CommandLine &CmdL) +{ + std::string sourceslist; + if (CmdL.FileList[1] != NULL) + { + sourceslist = _config->FindDir("Dir::Etc::sourceparts") + CmdL.FileList[1]; + if (!APT::String::Endswith(sourceslist, ".list")) + sourceslist += ".list"; + } else { + sourceslist = _config->FindFile("Dir::Etc::sourcelist"); + } + HashString before; + if (FileExists(sourceslist)) + before.FromFile(sourceslist); + else + { + FileFd filefd; + if (filefd.Open(sourceslist, FileFd::Create | FileFd::WriteOnly, FileFd::None, 0644) == false) + return false; + } + + ScopedGetLock lock(sourceslist); + if (lock.fd < 0) + return false; + + bool res; + bool file_changed = false; + do { + if (EditFileInSensibleEditor(sourceslist) == false) + return false; + if (before.empty()) + { + struct stat St; + if (stat(sourceslist.c_str(), &St) == 0 && St.st_size == 0) + RemoveFile("edit-sources", sourceslist); + } + else if (FileExists(sourceslist) && !before.VerifyFile(sourceslist)) + { + file_changed = true; + pkgCacheFile::RemoveCaches(); + } + pkgCacheFile CacheFile; + res = CacheFile.BuildCaches(nullptr); + if (res == false || _error->empty(GlobalError::WARNING) == false) { + std::string outs; + strprintf(outs, _("Failed to parse %s. Edit again? "), sourceslist.c_str()); + // FIXME: should we add a "restore previous" option here? + if (YnPrompt(outs.c_str(), true) == false) + { + if (res == false && _error->PendingError() == false) + { + CacheFile.Close(); + pkgCacheFile::RemoveCaches(); + res = CacheFile.BuildCaches(nullptr); + } + break; + } + } + } while (res == false); + + if (res == true && file_changed == true) + { + ioprintf( + std::cout, _("Your '%s' file changed, please run 'apt-get update'.\n"), + sourceslist.c_str()); + } + return res; +} + /*}}}*/ diff --git a/apt-private/private-sources.h b/apt-private/private-sources.h new file mode 100644 index 0000000..0c42190 --- /dev/null +++ b/apt-private/private-sources.h @@ -0,0 +1,10 @@ +#ifndef APT_PRIVATE_SOURCES_H +#define APT_PRIVATE_SOURCES_H + +#include <apt-pkg/macros.h> + +class CommandLine; + +APT_PUBLIC bool EditSources(CommandLine &CmdL); + +#endif diff --git a/apt-private/private-unmet.cc b/apt-private/private-unmet.cc new file mode 100644 index 0000000..f5161fc --- /dev/null +++ b/apt-private/private-unmet.cc @@ -0,0 +1,120 @@ +// -*- mode: cpp; mode: fold -*- +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/strutl.h> + +#include <apt-private/private-cacheset.h> +#include <apt-private/private-unmet.h> + +#include <stddef.h> + +#include <iostream> + +#include <apti18n.h> + /*}}}*/ + +// UnMet - Show unmet dependencies /*{{{*/ +static bool ShowUnMet(pkgCache::VerIterator const &V, bool const Important) +{ + bool Header = false; + for (pkgCache::DepIterator D = V.DependsList(); D.end() == false;) + { + // Collect or groups + pkgCache::DepIterator Start; + pkgCache::DepIterator End; + D.GlobOr(Start,End); + + // Important deps only + if (Important == true) + if (End->Type != pkgCache::Dep::PreDepends && + End->Type != pkgCache::Dep::Depends) + continue; + + // Skip conflicts and replaces + if (End.IsNegative() == true || End->Type == pkgCache::Dep::Replaces) + continue; + + // Verify the or group + bool OK = false; + pkgCache::DepIterator RealStart = Start; + do + { + // See if this dep is Ok + pkgCache::Version **VList = Start.AllTargets(); + if (*VList != 0) + { + OK = true; + delete [] VList; + break; + } + delete [] VList; + + if (Start == End) + break; + ++Start; + } + while (1); + + // The group is OK + if (OK == true) + continue; + + // Oops, it failed.. + if (Header == false) + ioprintf(std::cout,_("Package %s version %s has an unmet dep:\n"), + V.ParentPkg().FullName(true).c_str(),V.VerStr()); + Header = true; + + // Print out the dep type + std::cout << " " << End.DepType() << ": "; + + // Show the group + Start = RealStart; + do + { + std::cout << Start.TargetPkg().FullName(true); + if (Start.TargetVer() != 0) + std::cout << " (" << Start.CompType() << " " << Start.TargetVer() << + ")"; + if (Start == End) + break; + std::cout << " | "; + ++Start; + } + while (1); + + std::cout << std::endl; + } + return true; +} +bool UnMet(CommandLine &CmdL) +{ + bool const Important = _config->FindB("APT::Cache::Important",false); + + pkgCacheFile CacheFile; + if (unlikely(CacheFile.GetPkgCache() == NULL)) + return false; + + if (CmdL.FileSize() <= 1) + { + for (pkgCache::PkgIterator P = CacheFile.GetPkgCache()->PkgBegin(); P.end() == false; ++P) + for (pkgCache::VerIterator V = P.VersionList(); V.end() == false; ++V) + if (ShowUnMet(V, Important) == false) + return false; + } + else + { + CacheSetHelperVirtuals helper(true, GlobalError::NOTICE); + APT::VersionList verset = APT::VersionList::FromCommandLine(CacheFile, CmdL.FileList + 1, + APT::CacheSetHelper::CANDIDATE, helper); + for (APT::VersionList::iterator V = verset.begin(); V != verset.end(); ++V) + if (ShowUnMet(V, Important) == false) + return false; + } + return true; +} + /*}}}*/ diff --git a/apt-private/private-unmet.h b/apt-private/private-unmet.h new file mode 100644 index 0000000..31c2c17 --- /dev/null +++ b/apt-private/private-unmet.h @@ -0,0 +1,10 @@ +#ifndef APT_PRIVATE_UNMET_H +#define APT_PRIVATE_UNMET_H + +#include <apt-pkg/macros.h> + +class CommandLine; + +APT_PUBLIC bool UnMet(CommandLine &CmdL); + +#endif diff --git a/apt-private/private-update.cc b/apt-private/private-update.cc new file mode 100644 index 0000000..f1734fe --- /dev/null +++ b/apt-private/private-update.cc @@ -0,0 +1,258 @@ +// Include files /*{{{*/ +#include <config.h> + +#include <apt-pkg/acquire-item.h> +#include <apt-pkg/acquire.h> +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/metaindex.h> +#include <apt-pkg/sourcelist.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/update.h> + +#include <apt-private/acqprogress.h> +#include <apt-private/private-cachefile.h> +#include <apt-private/private-download.h> +#include <apt-private/private-output.h> +#include <apt-private/private-update.h> + +#include <ostream> +#include <string> +#include <tuple> + +#include <apti18n.h> + /*}}}*/ + +// DoUpdate - Update the package lists /*{{{*/ +static bool isDebianBookwormRelease(pkgCache::RlsFileIterator const &RlsFile) +{ + std::tuple<std::string_view, std::string_view, std::string_view> const affected[] = { + {"Debian", "Debian", "bookworm"}, + {"Debian", "Debian", "sid"}, + }; + if (RlsFile.end() || RlsFile->Origin == nullptr || RlsFile->Label == nullptr || RlsFile->Codename == nullptr) + return false; + std::tuple<std::string_view, std::string_view, std::string_view> const release{RlsFile.Origin(), RlsFile.Label(), RlsFile.Codename()}; + return std::find(std::begin(affected), std::end(affected), release) != std::end(affected); +} +static void suggestDebianNonFreeFirmware(char const *const repo, char const *const val, + char const *const from, char const *const to) +{ + // Both messages are reused from the ReleaseInfoChange feature in acquire-item.cc + _error->Notice(_("Repository '%s' changed its '%s' value from '%s' to '%s'"), repo, val, from, to); + std::string notes; + strprintf(notes, "https://www.debian.org/releases/bookworm/%s/release-notes/ch-information.html#non-free-split", _config->Find("APT::Architecture").c_str()); + _error->Notice(_("More information about this can be found online in the Release notes at: %s"), notes.c_str()); +} +bool DoUpdate(CommandLine &CmdL) +{ + if (CmdL.FileSize() != 1) + return _error->Error(_("The update command takes no arguments")); + + CacheFile Cache; + + // Get the source list + if (Cache.BuildSourceList() == false) + return false; + pkgSourceList *List = Cache.GetSourceList(); + + // Just print out the uris an exit if the --print-uris flag was used + if (_config->FindB("APT::Get::Print-URIs") == true) + { + // force a hashsum for compatibility reasons + _config->CndSet("Acquire::ForceHash", "md5sum"); + + // Populate it with the source selection and get all Indexes + // (GetAll=true) + aptAcquireWithTextStatus Fetcher; + if (List->GetIndexes(&Fetcher,true) == false) + return false; + + std::string compExt = APT::Configuration::getCompressionTypes()[0]; + pkgAcquire::UriIterator I = Fetcher.UriBegin(); + for (; I != Fetcher.UriEnd(); ++I) + { + std::string FileName = flNotDir(I->Owner->DestFile); + if(compExt.empty() == false && + APT::String::Endswith(FileName, compExt)) + FileName = FileName.substr(0, FileName.size() - compExt.size() - 1); + c1out << '\'' << I->URI << "' " << FileName << ' ' << + std::to_string(I->Owner->FileSize) << ' ' << I->Owner->HashSum() << std::endl; + } + return true; + } + + // do the work + if (_config->FindB("APT::Get::Download",true) == true) + { + AcqTextStatus Stat(std::cout, ScreenWidth,_config->FindI("quiet",0)); + ListUpdate(Stat, *List); + } + + if (_config->FindB("pkgCacheFile::Generate", true) == false) + return true; + + // Rebuild the cache. + pkgCacheFile::RemoveCaches(); + if (Cache.BuildCaches(false) == false) + return false; + + bool const SLWarnings = _config->FindB("APT::Get::Update::SourceListWarnings", true); + if (SLWarnings) + List = Cache.GetSourceList(); + + if (_config->FindB("APT::Get::Update::SourceListWarnings::APTAuth", SLWarnings)) + { + constexpr std::string_view const affected_method[] = {"http", "https", "tor+http", "tor+https", "ftp"}; + for (auto *S : *List) + { + URI uri(S->GetURI()); + if (uri.User.empty() && uri.Password.empty()) + continue; + // we can't really predict if a +http method supports everything http does, + // so we play it safe and use an allowlist here. + if (std::find(std::begin(affected_method), std::end(affected_method), uri.Access) != std::end(affected_method)) + // TRANSLATOR: the first two are manpage references, the last the URI from a sources.list + _error->Notice(_("Usage of %s should be preferred over embedding login information directly in the %s entry for '%s'"), + "apt_auth.conf(5)", "sources.list(5)", URI::ArchiveOnly(uri).c_str()); + } + } + + if (_config->FindB("APT::Get::Update::SourceListWarnings::NonFreeFirmware", SLWarnings)) + { + // If a Debian source has a non-free component, suggest adding non-free-firmware + bool found_affected_release = false; + bool found_non_free = false; + bool found_non_free_firmware = false; + for (auto *S : *List) + { + if (not isDebianBookwormRelease(S->FindInCache(Cache, false))) + continue; + + for (auto PkgFile = Cache.GetPkgCache()->FileBegin(); not PkgFile.end(); ++PkgFile) + { + if (PkgFile.Flagged(pkgCache::Flag::NoPackages)) + continue; + found_affected_release = true; + const auto * const comp = PkgFile.Component(); + if (comp == nullptr) + continue; + if (strcmp(comp, "non-free") == 0) + found_non_free = true; + else if (strcmp(comp, "non-free-firmware") == 0) + { + found_non_free_firmware = true; + break; + } + } + if (found_non_free_firmware) + break; + } + if (not found_non_free_firmware && found_non_free && found_affected_release) + { + /* See if a well-known firmware package is installable from this codename + if so, we likely operate with new apt on an old snapshot not supporting non-free-firmware */ + bool suggest_non_free_firmware = true; + if (auto const Grp = Cache.GetPkgCache()->FindGrp("firmware-linux-nonfree"); not Grp.end()) + { + for (auto Pkg = Grp.PackageList(); not Pkg.end() && suggest_non_free_firmware; Pkg = Grp.NextPkg(Pkg)) + { + for (auto Ver = Pkg.VersionList(); not Ver.end(); ++Ver) + { + if (not Ver.Downloadable()) + continue; + for (auto VerFile = Ver.FileList(); not VerFile.end(); ++VerFile) + { + auto const PkgFile = VerFile.File(); + if (PkgFile.end()) + continue; + if (not isDebianBookwormRelease(PkgFile.ReleaseFile())) + continue; + suggest_non_free_firmware = false; + break; + } + if (not suggest_non_free_firmware) + break; + } + } + } + if (suggest_non_free_firmware) + suggestDebianNonFreeFirmware("Debian bookworm", "non-free component", "non-free", "non-free non-free-firmware"); + } + + if (not found_non_free_firmware && not found_non_free && found_affected_release) + { + /* Try to notify users who have installed firmware packages at some point, but + have not enabled non-free currently – they might want to opt into updates now */ + APT::StringView const affected_pkgs[] = { + "amd64-microcode", "atmel-firmware", "bluez-firmware", "dahdi-firmware-nonfree", + "firmware-amd-graphics", "firmware-ast", "firmware-atheros", "firmware-bnx2", + "firmware-bnx2x", "firmware-brcm80211", "firmware-cavium", "firmware-intel-sound", + "firmware-intelwimax", "firmware-ipw2x00", "firmware-ivtv", "firmware-iwlwifi", + "firmware-libertas", "firmware-linux", "firmware-linux-nonfree", "firmware-misc-nonfree", + "firmware-myricom", "firmware-netronome", "firmware-netxen", "firmware-qcom-media", + "firmware-qcom-soc", "firmware-qlogic", "firmware-realtek", "firmware-realtek-rtl8723cs-bt", + "firmware-samsung", "firmware-siano", "firmware-sof-signed", "firmware-ti-connectivity", + "firmware-zd1211", "intel-microcode", "midisport-firmware", "raspi-firmware", + }; + bool suggest_non_free_firmware = false; + for (auto pkgname : affected_pkgs) + { + auto const Grp = Cache.GetPkgCache()->FindGrp(pkgname); + if (Grp.end()) + continue; + for (auto Pkg = Grp.PackageList(); not Pkg.end(); Pkg = Grp.NextPkg(Pkg)) + { + auto const Ver = Pkg.CurrentVer(); + if (Ver.end() || Ver.Downloadable()) + continue; + bool another = false; + for (auto V = Pkg.VersionList(); not V.end(); ++V) + if (V.Downloadable()) + { + another = true; + break; + } + if (another) + continue; + suggest_non_free_firmware = true; + break; + } + if (suggest_non_free_firmware) + break; + } + if (suggest_non_free_firmware) + suggestDebianNonFreeFirmware("Debian bookworm", "firmware component", "non-free", "non-free-firmware"); + } + } + + // show basic stats (if the user whishes) + if (_config->FindB("APT::Cmd::Show-Update-Stats", false) == true) + { + int upgradable = 0; + if (Cache.Open(false) == false) + return false; + for (pkgCache::PkgIterator I = Cache->PkgBegin(); I.end() != true; ++I) + { + pkgDepCache::StateCache &state = Cache[I]; + if (I->CurrentVer != 0 && state.Upgradable() && state.CandidateVer != NULL) + upgradable++; + } + const char *msg = P_( + "%i package can be upgraded. Run 'apt list --upgradable' to see it.\n", + "%i packages can be upgraded. Run 'apt list --upgradable' to see them.\n", + upgradable); + if (upgradable == 0) + c1out << _("All packages are up to date.") << std::endl; + else + ioprintf(c1out, msg, upgradable); + + RunScripts("APT::Update::Post-Invoke-Stats"); + } + + return true; +} + /*}}}*/ diff --git a/apt-private/private-update.h b/apt-private/private-update.h new file mode 100644 index 0000000..e584f70 --- /dev/null +++ b/apt-private/private-update.h @@ -0,0 +1,10 @@ +#ifndef APT_PRIVATE_UPDATE_H +#define APT_PRIVATE_UPDATE_H + +#include <apt-pkg/macros.h> + +class CommandLine; + +APT_PUBLIC bool DoUpdate(CommandLine &CmdL); + +#endif diff --git a/apt-private/private-upgrade.cc b/apt-private/private-upgrade.cc new file mode 100644 index 0000000..97603dc --- /dev/null +++ b/apt-private/private-upgrade.cc @@ -0,0 +1,78 @@ +// Includes /*{{{*/ +#include <config.h> + +#include <apt-pkg/cmndline.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/upgrade.h> + +#include <apt-private/private-cachefile.h> +#include <apt-private/private-install.h> +#include <apt-private/private-json-hooks.h> +#include <apt-private/private-output.h> +#include <apt-private/private-upgrade.h> + +#include <iostream> + +#include <apti18n.h> + /*}}}*/ + +// this is actually performing the various upgrade operations +static bool UpgradeHelper(CommandLine &CmdL, int UpgradeFlags) +{ + CacheFile Cache; + auto VolatileCmdL = GetPseudoPackages(Cache.GetSourceList(), CmdL, AddVolatileBinaryFile, ""); + + if (Cache.OpenForInstall() == false || Cache.CheckDeps() == false) + return false; + + std::map<unsigned short, APT::VersionVector> verset; + std::set<std::string> UnknownPackages; + APT::PackageVector HeldBackPackages; + if (not DoCacheManipulationFromCommandLine(CmdL, VolatileCmdL, Cache, verset, UpgradeFlags, UnknownPackages, HeldBackPackages)) + { + RunJsonHook("AptCli::Hooks::Upgrade", "org.debian.apt.hooks.install.fail", CmdL.FileList, Cache, UnknownPackages); + return false; + } + RunJsonHook("AptCli::Hooks::Upgrade", "org.debian.apt.hooks.install.pre-prompt", CmdL.FileList, Cache); + if (InstallPackages(Cache, HeldBackPackages, true, true, true, "AptCli::Hooks::Upgrade", CmdL)) + return RunJsonHook("AptCli::Hooks::Upgrade", "org.debian.apt.hooks.install.post", CmdL.FileList, Cache); + else + return RunJsonHook("AptCli::Hooks::Upgrade", "org.debian.apt.hooks.install.fail", CmdL.FileList, Cache); +} + +// DoDistUpgrade - Automatic smart upgrader /*{{{*/ +// --------------------------------------------------------------------- +/* Intelligent upgrader that will install and remove packages at will */ +bool DoDistUpgrade(CommandLine &CmdL) +{ + return UpgradeHelper(CmdL, APT::Upgrade::ALLOW_EVERYTHING); +} + /*}}}*/ +bool DoUpgrade(CommandLine &CmdL) /*{{{*/ +{ + if (_config->FindB("APT::Get::Upgrade-Allow-New", false) == true) + return DoUpgradeWithAllowNewPackages(CmdL); + else + return DoUpgradeNoNewPackages(CmdL); +} + /*}}}*/ +// DoUpgradeNoNewPackages - Upgrade all packages /*{{{*/ +// --------------------------------------------------------------------- +/* Upgrade all packages without installing new packages or erasing old + packages */ +bool DoUpgradeNoNewPackages(CommandLine &CmdL) +{ + // Do the upgrade + return UpgradeHelper(CmdL, + APT::Upgrade::FORBID_REMOVE_PACKAGES| + APT::Upgrade::FORBID_INSTALL_NEW_PACKAGES); +} + /*}}}*/ +// DoSafeUpgrade - Upgrade all packages with install but not remove /*{{{*/ +bool DoUpgradeWithAllowNewPackages(CommandLine &CmdL) +{ + return UpgradeHelper(CmdL, APT::Upgrade::FORBID_REMOVE_PACKAGES); +} + /*}}}*/ diff --git a/apt-private/private-upgrade.h b/apt-private/private-upgrade.h new file mode 100644 index 0000000..16bb93c --- /dev/null +++ b/apt-private/private-upgrade.h @@ -0,0 +1,13 @@ +#ifndef APTPRIVATE_PRIVATE_UPGRADE_H +#define APTPRIVATE_PRIVATE_UPGRADE_H + +#include <apt-pkg/macros.h> + +class CommandLine; + +APT_PUBLIC bool DoDistUpgrade(CommandLine &CmdL); +APT_PUBLIC bool DoUpgrade(CommandLine &CmdL); +bool DoUpgradeNoNewPackages(CommandLine &CmdL); +bool DoUpgradeWithAllowNewPackages(CommandLine &CmdL); + +#endif diff --git a/apt-private/private-utils.cc b/apt-private/private-utils.cc new file mode 100644 index 0000000..5863925 --- /dev/null +++ b/apt-private/private-utils.cc @@ -0,0 +1,98 @@ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> + +#include <apt-private/private-utils.h> + +#include <cstdlib> +#include <sstream> +#include <unistd.h> + +// DisplayFileInPager - Display File with pager /*{{{*/ +bool DisplayFileInPager(std::string const &filename) +{ + pid_t Process = ExecFork(); + if (Process == 0) + { + const char *Args[3]; + Args[1] = filename.c_str(); + Args[2] = NULL; + if (isatty(STDOUT_FILENO) == 1) + { + // likely installed, provided by sensible-utils + std::string const pager = _config->Find("Dir::Bin::Pager", + "sensible-pager"); + Args[0] = pager.c_str(); + execvp(Args[0],(char **)Args); + // lets try some obvious alternatives + Args[0] = getenv("PAGER"); + if (Args[0] != NULL) + execvp(Args[0],(char **)Args); + + Args[0] = "pager"; + execvp(Args[0],(char **)Args); + } + // we could read the file ourselves, but… meh + Args[0] = "cat"; + execvp(Args[0],(char **)Args); + exit(100); + } + + // Wait for the subprocess + return ExecWait(Process, "pager", false); +} + /*}}}*/ +// EditFileInSensibleEditor - Edit File with editor /*{{{*/ +bool EditFileInSensibleEditor(std::string const &filename) +{ + pid_t Process = ExecFork(); + if (Process == 0) + { + // likely installed, provided by sensible-utils + std::string const editor = _config->Find("Dir::Bin::Editor", + "sensible-editor"); + const char *Args[3]; + Args[0] = editor.c_str(); + Args[1] = filename.c_str(); + Args[2] = NULL; + execvp(Args[0],(char **)Args); + // the usual suspects we can try as an alternative + Args[0] = getenv("VISUAL"); + if (Args[0] != NULL) + execvp(Args[0],(char **)Args); + + Args[0] = getenv("EDITOR"); + if (Args[0] != NULL) + execvp(Args[0],(char **)Args); + + Args[0] = "editor"; + execvp(Args[0],(char **)Args); + exit(100); + } + + // Wait for the subprocess + return ExecWait(Process, "editor", false); +} + /*}}}*/ +time_t GetSecondsSinceEpoch() /*{{{*/ +{ + auto const source_date_epoch = getenv("SOURCE_DATE_EPOCH"); + if (source_date_epoch == nullptr) + return time(nullptr); + + time_t epoch; + std::stringstream ss(source_date_epoch); + ss >> epoch; + + if (ss.fail() || !ss.eof()) + { + _error->Warning("Environment variable SOURCE_DATE_EPOCH was ignored as it has an invalid value: \"%s\"", + source_date_epoch); + return time(nullptr); + } + + return epoch; +} + /*}}}*/ diff --git a/apt-private/private-utils.h b/apt-private/private-utils.h new file mode 100644 index 0000000..4d48bd1 --- /dev/null +++ b/apt-private/private-utils.h @@ -0,0 +1,10 @@ +#ifndef APT_PRIVATE_UTILS_H +#define APT_PRIVATE_UTILS_H + +#include <string> + +bool DisplayFileInPager(std::string const &filename); +bool EditFileInSensibleEditor(std::string const &filename); +time_t GetSecondsSinceEpoch(); + +#endif |