diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 18:07:13 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 18:07:13 +0000 |
commit | 636c7dc17286d93d788c741d15fd756aeda066d5 (patch) | |
tree | e7ae158cc54f591041a061b9865bcae51854f15c /apt-pkg | |
parent | Initial commit. (diff) | |
download | apt-636c7dc17286d93d788c741d15fd756aeda066d5.tar.xz apt-636c7dc17286d93d788c741d15fd756aeda066d5.zip |
Adding upstream version 1.8.2.3.upstream/1.8.2.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
137 files changed, 58342 insertions, 0 deletions
diff --git a/apt-pkg/CMakeLists.txt b/apt-pkg/CMakeLists.txt new file mode 100644 index 0000000..ce73c6a --- /dev/null +++ b/apt-pkg/CMakeLists.txt @@ -0,0 +1,76 @@ +# Include apt-pkg directly, as some files have #include <system.h> +include_directories(${PROJECT_BINARY_DIR}/include/apt-pkg) + +add_definitions("-DAPT_PKG_EXPOSE_STRING_VIEW") + +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/include/apt-pkg/) +execute_process(COMMAND ${PERL_EXECUTABLE} ${PROJECT_SOURCE_DIR}/triehash/triehash.pl + --ignore-case + --header ${PROJECT_BINARY_DIR}/include/apt-pkg/tagfile-keys.h + --code ${CMAKE_CURRENT_BINARY_DIR}/tagfile-keys.cc + --enum-class + --enum-name pkgTagSection::Key + --function-name pkgTagHash + --include "<apt-pkg/tagfile.h>" + ${CMAKE_CURRENT_SOURCE_DIR}/tagfile-keys.list) +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "tagfile-keys.list") + + +# Set the version of the library +execute_process(COMMAND awk -v ORS=. "/^\#define APT_PKG_M/ {print \$3}" + COMMAND sed "s/\\.\$//" + INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/contrib/macros.h + OUTPUT_VARIABLE MAJOR OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND grep "^#define APT_PKG_RELEASE" + COMMAND cut -d " " -f 3 + INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/contrib/macros.h + OUTPUT_VARIABLE MINOR OUTPUT_STRIP_TRAILING_WHITESPACE) + +message(STATUS "Building libapt-pkg ${MAJOR} (release ${MINOR})") +set(APT_PKG_MAJOR ${MAJOR} PARENT_SCOPE) # exporting for methods/CMakeLists.txt + +# 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" "${CMAKE_CURRENT_BINARY_DIR}/tagfile-keys.cc") +file(GLOB_RECURSE headers "*.h") + +# Create a library using the C++ files +add_library(apt-pkg SHARED ${library}) +add_dependencies(apt-pkg apt-pkg-versionscript) +# Link the library and set the SONAME +target_include_directories(apt-pkg + PRIVATE ${ZLIB_INCLUDE_DIRS} + ${BZIP2_INCLUDE_DIR} + ${LZMA_INCLUDE_DIRS} + ${LZ4_INCLUDE_DIRS} + $<$<BOOL:${ZSTD_FOUND}>:${ZSTD_INCLUDE_DIRS}> + $<$<BOOL:${UDEV_FOUND}>:${UDEV_INCLUDE_DIRS}> + $<$<BOOL:${SYSTEMD_FOUND}>:${SYSTEMD_INCLUDE_DIRS}> + ${ICONV_INCLUDE_DIRS} +) + +target_link_libraries(apt-pkg + PRIVATE -lutil ${CMAKE_DL_LIBS} ${RESOLV_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ${ZLIB_LIBRARIES} + ${BZIP2_LIBRARIES} + ${LZMA_LIBRARIES} + ${LZ4_LIBRARIES} + $<$<BOOL:${ZSTD_FOUND}>:${ZSTD_LIBRARIES}> + $<$<BOOL:${UDEV_FOUND}>:${UDEV_LIBRARIES}> + $<$<BOOL:${SYSTEMD_FOUND}>:${SYSTEMD_LIBRARIES}> + ${ICONV_LIBRARIES} +) +set_target_properties(apt-pkg PROPERTIES VERSION ${MAJOR}.${MINOR}) +set_target_properties(apt-pkg PROPERTIES SOVERSION ${MAJOR}) +add_version_script(apt-pkg) + +# Install the library and the header files +install(TARGETS apt-pkg LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(FILES ${headers} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/apt-pkg) +flatify(${PROJECT_BINARY_DIR}/include/apt-pkg/ "${headers}") + +if(CMAKE_BUILD_TYPE STREQUAL "Coverage") + target_link_libraries(apt-pkg PUBLIC noprofile) +endif() diff --git a/apt-pkg/acquire-item.cc b/apt-pkg/acquire-item.cc new file mode 100644 index 0000000..606c949 --- /dev/null +++ b/apt-pkg/acquire-item.cc @@ -0,0 +1,4078 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Acquire Item - Item to acquire + + Each item can download to exactly one file at a time. This means you + cannot create an item that fetches two uri's to two files at the same + time. The pkgAcqIndex class creates a second class upon instantiation + to fetch the other index files because of this. + + ##################################################################### */ + /*}}}*/ +// 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/aptconfiguration.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/gpgv.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/metaindex.h> +#include <apt-pkg/netrc.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/sourcelist.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile.h> + +#include <algorithm> +#include <ctime> +#include <chrono> +#include <iostream> +#include <memory> +#include <numeric> +#include <random> +#include <sstream> +#include <string> +#include <unordered_set> +#include <vector> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ + +using namespace std; + +static std::string GetPartialFileName(std::string const &file) /*{{{*/ +{ + std::string DestFile = _config->FindDir("Dir::State::lists") + "partial/"; + DestFile += file; + return DestFile; +} + /*}}}*/ +static std::string GetPartialFileNameFromURI(std::string const &uri) /*{{{*/ +{ + return GetPartialFileName(URItoFileName(uri)); +} + /*}}}*/ +static std::string GetFinalFileNameFromURI(std::string const &uri) /*{{{*/ +{ + return _config->FindDir("Dir::State::lists") + URItoFileName(uri); +} + /*}}}*/ +static std::string GetKeepCompressedFileName(std::string file, IndexTarget const &Target)/*{{{*/ +{ + if (Target.KeepCompressed == false) + return file; + + std::string const KeepCompressedAs = Target.Option(IndexTarget::KEEPCOMPRESSEDAS); + if (KeepCompressedAs.empty() == false) + { + std::string const ext = KeepCompressedAs.substr(0, KeepCompressedAs.find(' ')); + if (ext != "uncompressed") + file.append(".").append(ext); + } + return file; +} + /*}}}*/ +static std::string GetMergeDiffsPatchFileName(std::string const &Final, std::string const &Patch)/*{{{*/ +{ + // rred expects the patch as $FinalFile.ed.$patchname.gz + return Final + ".ed." + Patch + ".gz"; +} + /*}}}*/ +static std::string GetDiffsPatchFileName(std::string const &Final) /*{{{*/ +{ + // rred expects the patch as $FinalFile.ed + return Final + ".ed"; +} + /*}}}*/ +static std::string GetExistingFilename(std::string const &File) /*{{{*/ +{ + if (RealFileExists(File)) + return File; + for (auto const &type : APT::Configuration::getCompressorExtensions()) + { + std::string const Final = File + type; + if (RealFileExists(Final)) + return Final; + } + return ""; +} + /*}}}*/ +static std::string GetDiffIndexFileName(std::string const &Name) /*{{{*/ +{ + return Name + ".diff/Index"; +} + /*}}}*/ +static std::string GetDiffIndexURI(IndexTarget const &Target) /*{{{*/ +{ + return Target.URI + ".diff/Index"; +} + /*}}}*/ + +static void ReportMirrorFailureToCentral(pkgAcquire::Item const &I, std::string const &FailCode, std::string const &Details)/*{{{*/ +{ + // we only act if a mirror was used at all + if(I.UsedMirror.empty()) + return; +#if 0 + std::cerr << "\nReportMirrorFailure: " + << UsedMirror + << " Uri: " << DescURI() + << " FailCode: " + << FailCode << std::endl; +#endif + string const report = _config->Find("Methods::Mirror::ProblemReporting", + LIBEXEC_DIR "/apt-report-mirror-failure"); + if(!FileExists(report)) + return; + + std::vector<char const*> const Args = { + report.c_str(), + I.UsedMirror.c_str(), + I.DescURI().c_str(), + FailCode.c_str(), + Details.c_str(), + NULL + }; + + pid_t pid = ExecFork(); + if(pid < 0) + { + _error->Error("ReportMirrorFailure Fork failed"); + return; + } + else if(pid == 0) + { + execvp(Args[0], (char**)Args.data()); + std::cerr << "Could not exec " << Args[0] << std::endl; + _exit(100); + } + if(!ExecWait(pid, "report-mirror-failure")) + _error->Warning("Couldn't report problem to '%s'", report.c_str()); +} + /*}}}*/ + +static APT_NONNULL(2) bool MessageInsecureRepository(bool const isError, char const * const msg, std::string const &repo)/*{{{*/ +{ + std::string m; + strprintf(m, msg, repo.c_str()); + if (isError) + { + _error->Error("%s", m.c_str()); + _error->Notice("%s", _("Updating from such a repository can't be done securely, and is therefore disabled by default.")); + } + else + { + _error->Warning("%s", m.c_str()); + _error->Notice("%s", _("Data from such a repository can't be authenticated and is therefore potentially dangerous to use.")); + } + _error->Notice("%s", _("See apt-secure(8) manpage for repository creation and user configuration details.")); + return false; +} + /*}}}*/ +// AllowInsecureRepositories /*{{{*/ +enum class InsecureType { UNSIGNED, WEAK, NORELEASE }; +static bool TargetIsAllowedToBe(IndexTarget const &Target, InsecureType const type) +{ + if (_config->FindB("Acquire::AllowInsecureRepositories")) + return true; + + if (Target.OptionBool(IndexTarget::ALLOW_INSECURE)) + return true; + + switch (type) + { + case InsecureType::UNSIGNED: break; + case InsecureType::NORELEASE: break; + case InsecureType::WEAK: + if (_config->FindB("Acquire::AllowWeakRepositories")) + return true; + if (Target.OptionBool(IndexTarget::ALLOW_WEAK)) + return true; + break; + } + return false; +} +static bool APT_NONNULL(3, 4, 5) AllowInsecureRepositories(InsecureType const msg, std::string const &repo, + metaIndex const * const MetaIndexParser, pkgAcqMetaClearSig * const TransactionManager, pkgAcquire::Item * const I) +{ + // we skip weak downgrades as its unlikely that a repository gets really weaker – + // its more realistic that apt got pickier in a newer version + if (msg != InsecureType::WEAK) + { + std::string const FinalInRelease = TransactionManager->GetFinalFilename(); + std::string const FinalReleasegpg = FinalInRelease.substr(0, FinalInRelease.length() - strlen("InRelease")) + "Release.gpg"; + if (RealFileExists(FinalReleasegpg) || RealFileExists(FinalInRelease)) + { + char const * msgstr = nullptr; + switch (msg) + { + case InsecureType::UNSIGNED: msgstr = _("The repository '%s' is no longer signed."); break; + case InsecureType::NORELEASE: msgstr = _("The repository '%s' no longer has a Release file."); break; + case InsecureType::WEAK: /* unreachable */ break; + } + if (_config->FindB("Acquire::AllowDowngradeToInsecureRepositories") || + TransactionManager->Target.OptionBool(IndexTarget::ALLOW_DOWNGRADE_TO_INSECURE)) + { + // meh, the users wants to take risks (we still mark the packages + // from this repository as unauthenticated) + _error->Warning(msgstr, repo.c_str()); + _error->Warning(_("This is normally not allowed, but the option " + "Acquire::AllowDowngradeToInsecureRepositories was " + "given to override it.")); + } else { + MessageInsecureRepository(true, msgstr, repo); + TransactionManager->AbortTransaction(); + I->Status = pkgAcquire::Item::StatError; + return false; + } + } + } + + if(MetaIndexParser->GetTrusted() == metaIndex::TRI_YES) + return true; + + char const * msgstr = nullptr; + switch (msg) + { + case InsecureType::UNSIGNED: msgstr = _("The repository '%s' is not signed."); break; + case InsecureType::NORELEASE: msgstr = _("The repository '%s' does not have a Release file."); break; + case InsecureType::WEAK: msgstr = _("The repository '%s' provides only weak security information."); break; + } + + if (TargetIsAllowedToBe(TransactionManager->Target, msg) == true) + { + MessageInsecureRepository(false, msgstr, repo); + return true; + } + + MessageInsecureRepository(true, msgstr, repo); + TransactionManager->AbortTransaction(); + I->Status = pkgAcquire::Item::StatError; + return false; +} + /*}}}*/ +static HashStringList GetExpectedHashesFromFor(metaIndex * const Parser, std::string const &MetaKey)/*{{{*/ +{ + if (Parser == NULL) + return HashStringList(); + metaIndex::checkSum * const R = Parser->Lookup(MetaKey); + if (R == NULL) + return HashStringList(); + return R->Hashes; +} + /*}}}*/ + +class pkgAcquire::Item::Private /*{{{*/ +{ +public: + struct AlternateURI + { + std::string URI; + std::unordered_map<std::string, std::string> changefields; + AlternateURI(std::string &&u, decltype(changefields) &&cf) : URI(u), changefields(cf) {} + }; + std::list<AlternateURI> AlternativeURIs; + std::vector<std::string> BadAlternativeSites; + std::vector<std::string> PastRedirections; + std::unordered_map<std::string, std::string> CustomFields; + unsigned int Retries; + + Private() : Retries(_config->FindI("Acquire::Retries", 0)) + { + } +}; + /*}}}*/ + +// all ::HashesRequired and ::GetExpectedHashes implementations /*{{{*/ +/* ::GetExpectedHashes is abstract and has to be implemented by all subclasses. + It is best to implement it as broadly as possible, while ::HashesRequired defaults + to true and should be as restrictive as possible for false cases. Note that if + a hash is returned by ::GetExpectedHashes it must match. Only if it doesn't + ::HashesRequired is called to evaluate if its okay to have no hashes. */ +APT_PURE bool pkgAcqTransactionItem::HashesRequired() const +{ + /* signed repositories obviously have a parser and good hashes. + unsigned repositories, too, as even if we can't trust them for security, + we can at least trust them for integrity of the download itself. + Only repositories without a Release file can (obviously) not have + hashes – and they are very uncommon and strongly discouraged */ + if (TransactionManager->MetaIndexParser->GetLoadedSuccessfully() != metaIndex::TRI_YES) + return false; + if (TargetIsAllowedToBe(Target, InsecureType::WEAK)) + { + /* If we allow weak hashes, we check that we have some (weak) and then + declare hashes not needed. That will tip us in the right direction + as if hashes exist, they will be used, even if not required */ + auto const hsl = GetExpectedHashes(); + if (hsl.usable()) + return true; + if (hsl.empty() == false) + return false; + } + return true; +} +HashStringList pkgAcqTransactionItem::GetExpectedHashes() const +{ + return GetExpectedHashesFor(GetMetaKey()); +} + +APT_PURE bool pkgAcqMetaBase::HashesRequired() const +{ + // Release and co have no hashes 'by design'. + return false; +} +HashStringList pkgAcqMetaBase::GetExpectedHashes() const +{ + return HashStringList(); +} + +APT_PURE bool pkgAcqIndexDiffs::HashesRequired() const +{ + /* We can't check hashes of rred result as we don't know what the + hash of the file will be. We just know the hash of the patch(es), + the hash of the file they will apply on and the hash of the resulting + file. */ + if (State == StateFetchDiff) + return true; + return false; +} +HashStringList pkgAcqIndexDiffs::GetExpectedHashes() const +{ + if (State == StateFetchDiff) + return available_patches[0].download_hashes; + return HashStringList(); +} + +APT_PURE bool pkgAcqIndexMergeDiffs::HashesRequired() const +{ + /* @see #pkgAcqIndexDiffs::HashesRequired, with the difference that + we can check the rred result after all patches are applied as + we know the expected result rather than potentially apply more patches */ + if (State == StateFetchDiff) + return true; + return State == StateApplyDiff; +} +HashStringList pkgAcqIndexMergeDiffs::GetExpectedHashes() const +{ + if (State == StateFetchDiff) + return patch.download_hashes; + else if (State == StateApplyDiff) + return GetExpectedHashesFor(Target.MetaKey); + return HashStringList(); +} + +APT_PURE bool pkgAcqArchive::HashesRequired() const +{ + return LocalSource == false; +} +HashStringList pkgAcqArchive::GetExpectedHashes() const +{ + // figured out while parsing the records + return ExpectedHashes; +} + +APT_PURE bool pkgAcqFile::HashesRequired() const +{ + // supplied as parameter at creation time, so the caller decides + return ExpectedHashes.usable(); +} +HashStringList pkgAcqFile::GetExpectedHashes() const +{ + return ExpectedHashes; +} + /*}}}*/ +// Acquire::Item::QueueURI and specialisations from child classes /*{{{*/ +bool pkgAcquire::Item::QueueURI(pkgAcquire::ItemDesc &Item) +{ + Owner->Enqueue(Item); + return true; +} +/* The idea here is that an item isn't queued if it exists on disk and the + transition manager was a hit as this means that the files it contains + the checksums for can't be updated either (or they are and we are asking + for a hashsum mismatch to happen which helps nobody) */ +bool pkgAcqTransactionItem::QueueURI(pkgAcquire::ItemDesc &Item) +{ + if (TransactionManager->State != TransactionStarted) + { + if (_config->FindB("Debug::Acquire::Transaction", false)) + std::clog << "Skip " << Target.URI << " as transaction was already dealt with!" << std::endl; + return false; + } + std::string const FinalFile = GetFinalFilename(); + if (TransactionManager->IMSHit == true && FileExists(FinalFile) == true) + { + PartialFile = DestFile = FinalFile; + Status = StatDone; + return false; + } + // this ensures we rewrite only once and only the first step + auto const OldBaseURI = Target.Option(IndexTarget::BASE_URI); + if (OldBaseURI.empty() || APT::String::Startswith(Item.URI, OldBaseURI) == false) + return pkgAcquire::Item::QueueURI(Item); + // the given URI is our last resort + PushAlternativeURI(std::string(Item.URI), {}, false); + // If we got the InRelease file via a mirror, pick all indexes directly from this mirror, too + std::string SameMirrorURI; + if (TransactionManager->BaseURI.empty() == false && TransactionManager->UsedMirror.empty() == false && + URI::SiteOnly(Item.URI) != URI::SiteOnly(TransactionManager->BaseURI)) + { + auto ExtraPath = Item.URI.substr(OldBaseURI.length()); + auto newURI = flCombine(TransactionManager->BaseURI, std::move(ExtraPath)); + if (IsGoodAlternativeURI(newURI)) + { + SameMirrorURI = std::move(newURI); + PushAlternativeURI(std::string(SameMirrorURI), {}, false); + } + } + // add URI and by-hash based on it + if (AcquireByHash()) + { + // if we use the mirror transport, ask it for by-hash uris + // we need to stick to the same mirror only for non-unique filenames + auto const sameMirrorException = [&]() { + if (Item.URI.find("mirror") == std::string::npos) + return false; + ::URI uri(Item.URI); + return uri.Access == "mirror" || APT::String::Startswith(uri.Access, "mirror+") || + APT::String::Endswith(uri.Access, "+mirror") || uri.Access.find("+mirror+") != std::string::npos; + }(); + if (sameMirrorException) + SameMirrorURI.clear(); + // now add the actual by-hash uris + auto const Expected = GetExpectedHashes(); + auto const TargetHash = Expected.find(nullptr); + auto const PushByHashURI = [&](std::string U) { + if (unlikely(TargetHash == nullptr)) + return false; + auto const trailing_slash = U.find_last_of("/"); + if (unlikely(trailing_slash == std::string::npos)) + return false; + auto byhashSuffix = "/by-hash/" + TargetHash->HashType() + "/" + TargetHash->HashValue(); + U.replace(trailing_slash, U.length() - trailing_slash, std::move(byhashSuffix)); + PushAlternativeURI(std::move(U), {}, false); + return true; + }; + PushByHashURI(Item.URI); + if (SameMirrorURI.empty() == false && PushByHashURI(SameMirrorURI) == false) + SameMirrorURI.clear(); + } + // the last URI added is the first one tried + if (unlikely(PopAlternativeURI(Item.URI) == false)) + return false; + if (SameMirrorURI.empty() == false) + { + UsedMirror = TransactionManager->UsedMirror; + if (Item.Description.find(" ") != string::npos) + Item.Description.replace(0, Item.Description.find(" "), UsedMirror); + } + return pkgAcquire::Item::QueueURI(Item); +} +/* The transition manager InRelease itself (or its older sisters-in-law + Release & Release.gpg) is always queued as this allows us to rerun gpgv + on it to verify that we aren't stalled with old files */ +bool pkgAcqMetaBase::QueueURI(pkgAcquire::ItemDesc &Item) +{ + return pkgAcquire::Item::QueueURI(Item); +} +/* the Diff/Index needs to queue also the up-to-date complete index file + to ensure that the list cleaner isn't eating it */ +bool pkgAcqDiffIndex::QueueURI(pkgAcquire::ItemDesc &Item) +{ + if (pkgAcqTransactionItem::QueueURI(Item) == true) + return true; + QueueOnIMSHit(); + return false; +} + /*}}}*/ +// Acquire::Item::GetFinalFilename and specialisations for child classes /*{{{*/ +std::string pkgAcquire::Item::GetFinalFilename() const +{ + // Beware: Desc.URI is modified by redirections + return GetFinalFileNameFromURI(Desc.URI); +} +std::string pkgAcqDiffIndex::GetFinalFilename() const +{ + std::string const FinalFile = GetFinalFileNameFromURI(GetDiffIndexURI(Target)); + // we don't want recompress, so lets keep whatever we got + if (CurrentCompressionExtension == "uncompressed") + return FinalFile; + return FinalFile + "." + CurrentCompressionExtension; +} +std::string pkgAcqIndex::GetFinalFilename() const +{ + std::string const FinalFile = GetFinalFileNameFromURI(Target.URI); + return GetKeepCompressedFileName(FinalFile, Target); +} +std::string pkgAcqMetaSig::GetFinalFilename() const +{ + return GetFinalFileNameFromURI(Target.URI); +} +std::string pkgAcqBaseIndex::GetFinalFilename() const +{ + return GetFinalFileNameFromURI(Target.URI); +} +std::string pkgAcqMetaBase::GetFinalFilename() const +{ + return GetFinalFileNameFromURI(Target.URI); +} +std::string pkgAcqArchive::GetFinalFilename() const +{ + return _config->FindDir("Dir::Cache::Archives") + flNotDir(StoreFilename); +} + /*}}}*/ +// pkgAcqTransactionItem::GetMetaKey and specialisations for child classes /*{{{*/ +std::string pkgAcqTransactionItem::GetMetaKey() const +{ + return Target.MetaKey; +} +std::string pkgAcqIndex::GetMetaKey() const +{ + if (Stage == STAGE_DECOMPRESS_AND_VERIFY || CurrentCompressionExtension == "uncompressed") + return Target.MetaKey; + return Target.MetaKey + "." + CurrentCompressionExtension; +} +std::string pkgAcqDiffIndex::GetMetaKey() const +{ + auto const metakey = GetDiffIndexFileName(Target.MetaKey); + if (CurrentCompressionExtension == "uncompressed") + return metakey; + return metakey + "." + CurrentCompressionExtension; +} + /*}}}*/ +//pkgAcqTransactionItem::TransactionState and specialisations for child classes /*{{{*/ +bool pkgAcqTransactionItem::TransactionState(TransactionStates const state) +{ + bool const Debug = _config->FindB("Debug::Acquire::Transaction", false); + switch(state) + { + case TransactionStarted: _error->Fatal("Item %s changed to invalid transaction start state!", Target.URI.c_str()); break; + case TransactionAbort: + if(Debug == true) + std::clog << " Cancel: " << DestFile << std::endl; + if (Status == pkgAcquire::Item::StatIdle) + { + Status = pkgAcquire::Item::StatDone; + Dequeue(); + } + break; + case TransactionCommit: + if(PartialFile.empty() == false) + { + bool sameFile = (PartialFile == DestFile); + // we use symlinks on IMS-Hit to avoid copies + if (RealFileExists(DestFile)) + { + struct stat Buf; + if (lstat(PartialFile.c_str(), &Buf) != -1) + { + if (S_ISLNK(Buf.st_mode) && Buf.st_size > 0) + { + char partial[Buf.st_size + 1]; + ssize_t const sp = readlink(PartialFile.c_str(), partial, Buf.st_size); + if (sp == -1) + _error->Errno("pkgAcqTransactionItem::TransactionState-sp", _("Failed to readlink %s"), PartialFile.c_str()); + else + { + partial[sp] = '\0'; + sameFile = (DestFile == partial); + } + } + } + else + _error->Errno("pkgAcqTransactionItem::TransactionState-stat", _("Failed to stat %s"), PartialFile.c_str()); + } + if (sameFile == false) + { + // ensure that even without lists-cleanup all compressions are nuked + std::string FinalFile = GetFinalFileNameFromURI(Target.URI); + if (FileExists(FinalFile)) + { + if(Debug == true) + std::clog << "rm " << FinalFile << " # " << DescURI() << std::endl; + if (RemoveFile("TransactionStates-Cleanup", FinalFile) == false) + return false; + } + for (auto const &ext: APT::Configuration::getCompressorExtensions()) + { + auto const Final = FinalFile + ext; + if (FileExists(Final)) + { + if(Debug == true) + std::clog << "rm " << Final << " # " << DescURI() << std::endl; + if (RemoveFile("TransactionStates-Cleanup", Final) == false) + return false; + } + } + if(Debug == true) + std::clog << "mv " << PartialFile << " -> "<< DestFile << " # " << DescURI() << std::endl; + if (Rename(PartialFile, DestFile) == false) + return false; + } + else if(Debug == true) + std::clog << "keep " << PartialFile << " # " << DescURI() << std::endl; + + } else { + if(Debug == true) + std::clog << "rm " << DestFile << " # " << DescURI() << std::endl; + if (RemoveFile("TransItem::TransactionCommit", DestFile) == false) + return false; + } + break; + } + return true; +} +bool pkgAcqMetaBase::TransactionState(TransactionStates const state) +{ + // Do not remove InRelease on IMSHit of Release.gpg [yes, this is very edgecasey] + if (TransactionManager->IMSHit == false) + return pkgAcqTransactionItem::TransactionState(state); + return true; +} +bool pkgAcqIndex::TransactionState(TransactionStates const state) +{ + if (pkgAcqTransactionItem::TransactionState(state) == false) + return false; + + switch (state) + { + case TransactionStarted: _error->Fatal("AcqIndex %s changed to invalid transaction start state!", Target.URI.c_str()); break; + case TransactionAbort: + if (Stage == STAGE_DECOMPRESS_AND_VERIFY) + { + // keep the compressed file, but drop the decompressed + EraseFileName.clear(); + if (PartialFile.empty() == false && flExtension(PartialFile) != CurrentCompressionExtension) + RemoveFile("TransactionAbort", PartialFile); + } + break; + case TransactionCommit: + if (EraseFileName.empty() == false) + RemoveFile("AcqIndex::TransactionCommit", EraseFileName); + break; + } + return true; +} +bool pkgAcqDiffIndex::TransactionState(TransactionStates const state) +{ + if (pkgAcqTransactionItem::TransactionState(state) == false) + return false; + + switch (state) + { + case TransactionStarted: _error->Fatal("Item %s changed to invalid transaction start state!", Target.URI.c_str()); break; + case TransactionCommit: + break; + case TransactionAbort: + std::string const Partial = GetPartialFileNameFromURI(Target.URI); + RemoveFile("TransactionAbort", Partial); + break; + } + + return true; +} + /*}}}*/ +// pkgAcqTransactionItem::AcquireByHash and specialisations for child classes /*{{{*/ +bool pkgAcqTransactionItem::AcquireByHash() const +{ + if (TransactionManager->MetaIndexParser == nullptr) + return false; + auto const useByHashConf = Target.Option(IndexTarget::BY_HASH); + if (useByHashConf == "force") + return true; + return StringToBool(useByHashConf) == true && TransactionManager->MetaIndexParser->GetSupportsAcquireByHash(); +} +// pdiff patches have a unique name already, no need for by-hash +bool pkgAcqIndexMergeDiffs::AcquireByHash() const +{ + return false; +} +bool pkgAcqIndexDiffs::AcquireByHash() const +{ + return false; +} + /*}}}*/ + +class APT_HIDDEN NoActionItem : public pkgAcquire::Item /*{{{*/ +/* The sole purpose of this class is having an item which does nothing to + reach its done state to prevent cleanup deleting the mentioned file. + Handy in cases in which we know we have the file already, like IMS-Hits. */ +{ + IndexTarget const Target; + public: + virtual std::string DescURI() const APT_OVERRIDE {return Target.URI;}; + virtual HashStringList GetExpectedHashes() const APT_OVERRIDE {return HashStringList();}; + + NoActionItem(pkgAcquire * const Owner, IndexTarget const &Target) : + pkgAcquire::Item(Owner), Target(Target) + { + Status = StatDone; + DestFile = GetFinalFileNameFromURI(Target.URI); + } + NoActionItem(pkgAcquire * const Owner, IndexTarget const &Target, std::string const &FinalFile) : + pkgAcquire::Item(Owner), Target(Target) + { + Status = StatDone; + DestFile = FinalFile; + } +}; + /*}}}*/ +class APT_HIDDEN CleanupItem : public pkgAcqTransactionItem /*{{{*/ +/* This class ensures that a file which was configured but isn't downloaded + for various reasons isn't kept in an old version in the lists directory. + In a way its the reverse of NoActionItem as it helps with removing files + even if the lists-cleanup is deactivated. */ +{ + public: + virtual std::string DescURI() const APT_OVERRIDE {return Target.URI;}; + virtual HashStringList GetExpectedHashes() const APT_OVERRIDE {return HashStringList();}; + + CleanupItem(pkgAcquire * const Owner, pkgAcqMetaClearSig * const TransactionManager, IndexTarget const &Target) : + pkgAcqTransactionItem(Owner, TransactionManager, Target) + { + Status = StatDone; + DestFile = GetFinalFileNameFromURI(Target.URI); + } + bool TransactionState(TransactionStates const state) APT_OVERRIDE + { + switch (state) + { + case TransactionStarted: + break; + case TransactionAbort: + break; + case TransactionCommit: + if (_config->FindB("Debug::Acquire::Transaction", false) == true) + std::clog << "rm " << DestFile << " # " << DescURI() << std::endl; + if (RemoveFile("TransItem::TransactionCommit", DestFile) == false) + return false; + break; + } + return true; + } +}; + /*}}}*/ + +// Acquire::Item::Item - Constructor /*{{{*/ +APT_IGNORE_DEPRECATED_PUSH +pkgAcquire::Item::Item(pkgAcquire * const owner) : + FileSize(0), PartialSize(0), Mode(0), ID(0), Complete(false), Local(false), + QueueCounter(0), ExpectedAdditionalItems(0), Owner(owner), d(new Private()) +{ + Owner->Add(this); + Status = StatIdle; +} +APT_IGNORE_DEPRECATED_POP + /*}}}*/ +// Acquire::Item::~Item - Destructor /*{{{*/ +pkgAcquire::Item::~Item() +{ + Owner->Remove(this); + delete d; +} + /*}}}*/ +std::string pkgAcquire::Item::Custom600Headers() const /*{{{*/ +{ + std::ostringstream header; + for (auto const &f : d->CustomFields) + if (f.second.empty() == false) + header << '\n' + << f.first << ": " << f.second; + return header.str(); +} + /*}}}*/ +std::unordered_map<std::string, std::string> &pkgAcquire::Item::ModifyCustomFields() /*{{{*/ +{ + return d->CustomFields; +} + /*}}}*/ +bool pkgAcquire::Item::PopAlternativeURI(std::string &NewURI) /*{{{*/ +{ + if (d->AlternativeURIs.empty()) + return false; + auto const AltUri = d->AlternativeURIs.front(); + d->AlternativeURIs.pop_front(); + NewURI = AltUri.URI; + auto &CustomFields = ModifyCustomFields(); + for (auto const &f : AltUri.changefields) + { + if (f.second.empty()) + CustomFields.erase(f.first); + else + CustomFields[f.first] = f.second; + } + return true; +} + /*}}}*/ +bool pkgAcquire::Item::IsGoodAlternativeURI(std::string const &AltUri) const/*{{{*/ +{ + return std::find(d->PastRedirections.cbegin(), d->PastRedirections.cend(), AltUri) == d->PastRedirections.cend() && + std::find(d->BadAlternativeSites.cbegin(), d->BadAlternativeSites.cend(), URI::SiteOnly(AltUri)) == d->BadAlternativeSites.cend(); +} + /*}}}*/ +void pkgAcquire::Item::PushAlternativeURI(std::string &&NewURI, std::unordered_map<std::string, std::string> &&fields, bool const at_the_back) /*{{{*/ +{ + if (IsGoodAlternativeURI(NewURI) == false) + return; + if (at_the_back) + d->AlternativeURIs.emplace_back(std::move(NewURI), std::move(fields)); + else + d->AlternativeURIs.emplace_front(std::move(NewURI), std::move(fields)); +} + /*}}}*/ +void pkgAcquire::Item::RemoveAlternativeSite(std::string &&OldSite) /*{{{*/ +{ + d->AlternativeURIs.erase(std::remove_if(d->AlternativeURIs.begin(), d->AlternativeURIs.end(), + [&](decltype(*d->AlternativeURIs.cbegin()) AltUri) { + return URI::SiteOnly(AltUri.URI) == OldSite; + }), + d->AlternativeURIs.end()); + d->BadAlternativeSites.push_back(std::move(OldSite)); +} + /*}}}*/ +unsigned int &pkgAcquire::Item::ModifyRetries() /*{{{*/ +{ + return d->Retries; +} + /*}}}*/ +std::string pkgAcquire::Item::ShortDesc() const /*{{{*/ +{ + return DescURI(); +} + /*}}}*/ +void pkgAcquire::Item::Finished() /*{{{*/ +{ +} + /*}}}*/ +APT_PURE pkgAcquire * pkgAcquire::Item::GetOwner() const /*{{{*/ +{ + return Owner; +} + /*}}}*/ +APT_PURE pkgAcquire::ItemDesc &pkgAcquire::Item::GetItemDesc() /*{{{*/ +{ + return Desc; +} + /*}}}*/ +APT_PURE bool pkgAcquire::Item::IsTrusted() const /*{{{*/ +{ + return false; +} + /*}}}*/ +// Acquire::Item::Failed - Item failed to download /*{{{*/ +// --------------------------------------------------------------------- +/* We return to an idle state if there are still other queues that could + fetch this object */ +static void formatHashsum(std::ostream &out, HashString const &hs) +{ + auto const type = hs.HashType(); + if (type == "Checksum-FileSize") + out << " - Filesize"; + else + out << " - " << type; + out << ':' << hs.HashValue(); + if (hs.usable() == false) + out << " [weak]"; + out << std::endl; +} +void pkgAcquire::Item::Failed(string const &Message,pkgAcquire::MethodConfig const * const Cnf) +{ + if (QueueCounter <= 1) + { + /* This indicates that the file is not available right now but might + be sometime later. If we do a retry cycle then this should be + retried [CDROMs] */ + if (Cnf != NULL && Cnf->LocalOnly == true && + StringToBool(LookupTag(Message,"Transient-Failure"),false) == true) + { + Status = StatIdle; + Dequeue(); + return; + } + + switch (Status) + { + case StatIdle: + case StatFetching: + case StatDone: + Status = StatError; + break; + case StatAuthError: + case StatError: + case StatTransientNetworkError: + break; + } + Complete = false; + Dequeue(); + } + + FailMessage(Message); + + if (QueueCounter > 1) + Status = StatIdle; +} +void pkgAcquire::Item::FailMessage(string const &Message) +{ + string const FailReason = LookupTag(Message, "FailReason"); + enum { MAXIMUM_SIZE_EXCEEDED, HASHSUM_MISMATCH, WEAK_HASHSUMS, REDIRECTION_LOOP, OTHER } failreason = OTHER; + if ( FailReason == "MaximumSizeExceeded") + failreason = MAXIMUM_SIZE_EXCEEDED; + else if ( FailReason == "WeakHashSums") + failreason = WEAK_HASHSUMS; + else if (FailReason == "RedirectionLoop") + failreason = REDIRECTION_LOOP; + else if (Status == StatAuthError) + failreason = HASHSUM_MISMATCH; + + if(ErrorText.empty()) + { + std::ostringstream out; + switch (failreason) + { + case HASHSUM_MISMATCH: + out << _("Hash Sum mismatch") << std::endl; + break; + case WEAK_HASHSUMS: + out << _("Insufficient information available to perform this download securely") << std::endl; + break; + case REDIRECTION_LOOP: + out << "Redirection loop encountered" << std::endl; + break; + case MAXIMUM_SIZE_EXCEEDED: + out << LookupTag(Message, "Message") << std::endl; + break; + case OTHER: + out << LookupTag(Message, "Message"); + break; + } + + if (Status == StatAuthError) + { + auto const ExpectedHashes = GetExpectedHashes(); + if (ExpectedHashes.empty() == false) + { + out << "Hashes of expected file:" << std::endl; + for (auto const &hs: ExpectedHashes) + formatHashsum(out, hs); + } + if (failreason == HASHSUM_MISMATCH) + { + out << "Hashes of received file:" << std::endl; + for (char const * const * type = HashString::SupportedHashes(); *type != NULL; ++type) + { + std::string const tagname = std::string(*type) + "-Hash"; + std::string const hashsum = LookupTag(Message, tagname.c_str()); + if (hashsum.empty() == false) + formatHashsum(out, HashString(*type, hashsum)); + } + } + auto const lastmod = LookupTag(Message, "Last-Modified", ""); + if (lastmod.empty() == false) + out << "Last modification reported: " << lastmod << std::endl; + } + ErrorText = out.str(); + } + + switch (failreason) + { + case MAXIMUM_SIZE_EXCEEDED: RenameOnError(MaximumSizeExceeded); break; + case HASHSUM_MISMATCH: RenameOnError(HashSumMismatch); break; + case WEAK_HASHSUMS: break; + case REDIRECTION_LOOP: break; + case OTHER: break; + } + + if (FailReason.empty() == false) + ReportMirrorFailureToCentral(*this, FailReason, ErrorText); + else + ReportMirrorFailureToCentral(*this, ErrorText, ErrorText); +} + /*}}}*/ +// Acquire::Item::Start - Item has begun to download /*{{{*/ +// --------------------------------------------------------------------- +/* Stash status and the file size. Note that setting Complete means + sub-phases of the acquire process such as decompresion are operating */ +void pkgAcquire::Item::Start(string const &/*Message*/, unsigned long long const Size) +{ + Status = StatFetching; + ErrorText.clear(); + if (FileSize == 0 && Complete == false) + FileSize = Size; +} + /*}}}*/ +// Acquire::Item::VerifyDone - check if Item was downloaded OK /*{{{*/ +/* Note that hash-verification is 'hardcoded' in acquire-worker and has + * already passed if this method is called. */ +bool pkgAcquire::Item::VerifyDone(std::string const &Message, + pkgAcquire::MethodConfig const * const /*Cnf*/) +{ + std::string const FileName = LookupTag(Message,"Filename"); + if (FileName.empty() == true) + { + Status = StatError; + ErrorText = "Method gave a blank filename"; + return false; + } + + return true; +} + /*}}}*/ +// Acquire::Item::Done - Item downloaded OK /*{{{*/ +void pkgAcquire::Item::Done(string const &/*Message*/, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const /*Cnf*/) +{ + // We just downloaded something.. + if (FileSize == 0) + { + unsigned long long const downloadedSize = Hashes.FileSize(); + if (downloadedSize != 0) + { + FileSize = downloadedSize; + } + } + Status = StatDone; + ErrorText.clear(); + Dequeue(); +} + /*}}}*/ +// Acquire::Item::Rename - Rename a file /*{{{*/ +// --------------------------------------------------------------------- +/* This helper function is used by a lot of item methods as their final + step */ +bool pkgAcquire::Item::Rename(string const &From,string const &To) +{ + if (From == To || rename(From.c_str(),To.c_str()) == 0) + return true; + + std::string S; + strprintf(S, _("rename failed, %s (%s -> %s)."), strerror(errno), + From.c_str(),To.c_str()); + Status = StatError; + if (ErrorText.empty()) + ErrorText = S; + else + ErrorText = ErrorText + ": " + S; + return false; +} + /*}}}*/ +void pkgAcquire::Item::Dequeue() /*{{{*/ +{ + d->AlternativeURIs.clear(); + Owner->Dequeue(this); +} + /*}}}*/ +bool pkgAcquire::Item::RenameOnError(pkgAcquire::Item::RenameOnErrorState const error)/*{{{*/ +{ + if (RealFileExists(DestFile)) + Rename(DestFile, DestFile + ".FAILED"); + + std::string errtext; + switch (error) + { + case HashSumMismatch: + errtext = _("Hash Sum mismatch"); + break; + case SizeMismatch: + errtext = _("Size mismatch"); + Status = StatAuthError; + break; + case InvalidFormat: + errtext = _("Invalid file format"); + Status = StatError; + // do not report as usually its not the mirrors fault, but Portal/Proxy + break; + case SignatureError: + errtext = _("Signature error"); + Status = StatError; + break; + case NotClearsigned: + strprintf(errtext, _("Clearsigned file isn't valid, got '%s' (does the network require authentication?)"), "NOSPLIT"); + Status = StatAuthError; + break; + case MaximumSizeExceeded: + // the method is expected to report a good error for this + break; + case PDiffError: + // no handling here, done by callers + break; + } + if (ErrorText.empty()) + ErrorText = errtext; + return false; +} + /*}}}*/ +void pkgAcquire::Item::SetActiveSubprocess(const std::string &subprocess)/*{{{*/ +{ + ActiveSubprocess = subprocess; + APT_IGNORE_DEPRECATED_PUSH + Mode = ActiveSubprocess.c_str(); + APT_IGNORE_DEPRECATED_POP +} + /*}}}*/ +// Acquire::Item::ReportMirrorFailure /*{{{*/ +void pkgAcquire::Item::ReportMirrorFailure(std::string const &FailCode) +{ + ReportMirrorFailureToCentral(*this, FailCode, FailCode); +} + /*}}}*/ +std::string pkgAcquire::Item::HashSum() const /*{{{*/ +{ + HashStringList const hashes = GetExpectedHashes(); + HashString const * const hs = hashes.find(NULL); + return hs != NULL ? hs->toStr() : ""; +} + /*}}}*/ +bool pkgAcquire::Item::IsRedirectionLoop(std::string const &NewURI) /*{{{*/ +{ + // store can fail due to permission errors and the item will "loop" then + if (APT::String::Startswith(NewURI, "store:")) + return false; + if (d->PastRedirections.empty()) + { + d->PastRedirections.push_back(NewURI); + return false; + } + auto const LastURI = std::prev(d->PastRedirections.end()); + // redirections to the same file are a way of restarting/resheduling, + // individual methods will have to make sure that they aren't looping this way + if (*LastURI == NewURI) + return false; + if (std::find(d->PastRedirections.begin(), LastURI, NewURI) != LastURI) + return true; + d->PastRedirections.push_back(NewURI); + return false; +} + /*}}}*/ +int pkgAcquire::Item::Priority() /*{{{*/ +{ + // Stage 0: Files requested by methods + // - they will usually not end up here, but if they do we make sure + // to get them as soon as possible as they are probably blocking + // the processing of files by the requesting method + if (dynamic_cast<pkgAcqAuxFile *>(this) != nullptr) + return 5000; + // Stage 1: Meta indices and diff indices + // - those need to be fetched first to have progress reporting working + // for the rest + if (dynamic_cast<pkgAcqMetaSig*>(this) != nullptr + || dynamic_cast<pkgAcqMetaBase*>(this) != nullptr + || dynamic_cast<pkgAcqDiffIndex*>(this) != nullptr) + return 1000; + // Stage 2: Diff files + // - fetch before complete indexes so we can apply the diffs while fetching + // larger files. + if (dynamic_cast<pkgAcqIndexDiffs*>(this) != nullptr || + dynamic_cast<pkgAcqIndexMergeDiffs*>(this) != nullptr) + return 800; + + // Stage 3: The rest - complete index files and other stuff + return 500; +} + /*}}}*/ + +pkgAcqTransactionItem::pkgAcqTransactionItem(pkgAcquire * const Owner, /*{{{*/ + pkgAcqMetaClearSig * const transactionManager, IndexTarget const &target) : + pkgAcquire::Item(Owner), d(NULL), Target(target), TransactionManager(transactionManager) +{ + if (TransactionManager != this) + TransactionManager->Add(this); + ModifyCustomFields() = { + {"Target-Site", Target.Option(IndexTarget::SITE)}, + {"Target-Repo-URI", Target.Option(IndexTarget::REPO_URI)}, + {"Target-Base-URI", Target.Option(IndexTarget::BASE_URI)}, + {"Target-Component", Target.Option(IndexTarget::COMPONENT)}, + {"Target-Release", Target.Option(IndexTarget::RELEASE)}, + {"Target-Architecture", Target.Option(IndexTarget::ARCHITECTURE)}, + {"Target-Language", Target.Option(IndexTarget::LANGUAGE)}, + {"Target-Type", "index"}, + }; +} + /*}}}*/ +pkgAcqTransactionItem::~pkgAcqTransactionItem() /*{{{*/ +{ +} + /*}}}*/ +HashStringList pkgAcqTransactionItem::GetExpectedHashesFor(std::string const &MetaKey) const /*{{{*/ +{ + return GetExpectedHashesFromFor(TransactionManager->MetaIndexParser, MetaKey); +} + /*}}}*/ + +static void LoadLastMetaIndexParser(pkgAcqMetaClearSig * const TransactionManager, std::string const &FinalRelease, std::string const &FinalInRelease)/*{{{*/ +{ + if (TransactionManager->IMSHit == true) + return; + if (RealFileExists(FinalInRelease) || RealFileExists(FinalRelease)) + { + TransactionManager->LastMetaIndexParser = TransactionManager->MetaIndexParser->UnloadedClone(); + if (TransactionManager->LastMetaIndexParser != NULL) + { + _error->PushToStack(); + if (RealFileExists(FinalInRelease)) + TransactionManager->LastMetaIndexParser->Load(FinalInRelease, NULL); + else + TransactionManager->LastMetaIndexParser->Load(FinalRelease, NULL); + // its unlikely to happen, but if what we have is bad ignore it + if (_error->PendingError()) + { + delete TransactionManager->LastMetaIndexParser; + TransactionManager->LastMetaIndexParser = NULL; + } + _error->RevertToStack(); + } + } +} + /*}}}*/ + +// AcqMetaBase - Constructor /*{{{*/ +pkgAcqMetaBase::pkgAcqMetaBase(pkgAcquire * const Owner, + pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &DataTarget) +: pkgAcqTransactionItem(Owner, TransactionManager, DataTarget), d(NULL), + AuthPass(false), IMSHit(false), State(TransactionStarted) +{ +} + /*}}}*/ +// AcqMetaBase::Add - Add a item to the current Transaction /*{{{*/ +void pkgAcqMetaBase::Add(pkgAcqTransactionItem * const I) +{ + Transaction.push_back(I); +} + /*}}}*/ +// AcqMetaBase::AbortTransaction - Abort the current Transaction /*{{{*/ +void pkgAcqMetaBase::AbortTransaction() +{ + if(_config->FindB("Debug::Acquire::Transaction", false) == true) + std::clog << "AbortTransaction: " << TransactionManager << std::endl; + + switch (TransactionManager->State) + { + case TransactionStarted: break; + case TransactionAbort: _error->Fatal("Transaction %s was already aborted and is aborted again", TransactionManager->Target.URI.c_str()); return; + case TransactionCommit: _error->Fatal("Transaction %s was already aborted and is now committed", TransactionManager->Target.URI.c_str()); return; + } + TransactionManager->State = TransactionAbort; + TransactionManager->ExpectedAdditionalItems = 0; + + // ensure the toplevel is in error state too + for (std::vector<pkgAcqTransactionItem*>::iterator I = Transaction.begin(); + I != Transaction.end(); ++I) + { + (*I)->ExpectedAdditionalItems = 0; + if ((*I)->Status != pkgAcquire::Item::StatFetching) + (*I)->Dequeue(); + (*I)->TransactionState(TransactionAbort); + } + Transaction.clear(); +} + /*}}}*/ +// AcqMetaBase::TransactionHasError - Check for errors in Transaction /*{{{*/ +APT_PURE bool pkgAcqMetaBase::TransactionHasError() const +{ + for (std::vector<pkgAcqTransactionItem*>::const_iterator I = Transaction.begin(); + I != Transaction.end(); ++I) + { + switch((*I)->Status) { + case StatDone: break; + case StatIdle: break; + case StatAuthError: return true; + case StatError: return true; + case StatTransientNetworkError: return true; + case StatFetching: break; + } + } + return false; +} + /*}}}*/ +// AcqMetaBase::CommitTransaction - Commit a transaction /*{{{*/ +void pkgAcqMetaBase::CommitTransaction() +{ + if(_config->FindB("Debug::Acquire::Transaction", false) == true) + std::clog << "CommitTransaction: " << this << std::endl; + + switch (TransactionManager->State) + { + case TransactionStarted: break; + case TransactionAbort: _error->Fatal("Transaction %s was already committed and is now aborted", TransactionManager->Target.URI.c_str()); return; + case TransactionCommit: _error->Fatal("Transaction %s was already committed and is again committed", TransactionManager->Target.URI.c_str()); return; + } + TransactionManager->State = TransactionCommit; + + // move new files into place *and* remove files that are not + // part of the transaction but are still on disk + for (std::vector<pkgAcqTransactionItem*>::iterator I = Transaction.begin(); + I != Transaction.end(); ++I) + { + (*I)->TransactionState(TransactionCommit); + } + Transaction.clear(); +} + /*}}}*/ +// AcqMetaBase::TransactionStageCopy - Stage a file for copying /*{{{*/ +void pkgAcqMetaBase::TransactionStageCopy(pkgAcqTransactionItem * const I, + const std::string &From, + const std::string &To) +{ + I->PartialFile = From; + I->DestFile = To; +} + /*}}}*/ +// AcqMetaBase::TransactionStageRemoval - Stage a file for removal /*{{{*/ +void pkgAcqMetaBase::TransactionStageRemoval(pkgAcqTransactionItem * const I, + const std::string &FinalFile) +{ + I->PartialFile = ""; + I->DestFile = FinalFile; +} + /*}}}*/ +// AcqMetaBase::GenerateAuthWarning - Check gpg authentication error /*{{{*/ +/* This method is called from ::Failed handlers. If it returns true, + no fallback to other files or modi is performed */ +bool pkgAcqMetaBase::CheckStopAuthentication(pkgAcquire::Item * const I, const std::string &Message) +{ + string const Final = I->GetFinalFilename(); + std::string const GPGError = LookupTag(Message, "Message"); + if (FileExists(Final)) + { + I->Status = StatTransientNetworkError; + _error->Warning(_("An error occurred during the signature verification. " + "The repository is not updated and the previous index files will be used. " + "GPG error: %s: %s"), + Desc.Description.c_str(), + GPGError.c_str()); + RunScripts("APT::Update::Auth-Failure"); + return true; + } else if (LookupTag(Message,"Message").find("NODATA") != string::npos) { + /* Invalid signature file, reject (LP: #346386) (Closes: #627642) */ + _error->Error(_("GPG error: %s: %s"), + Desc.Description.c_str(), + GPGError.c_str()); + I->Status = StatAuthError; + return true; + } else { + _error->Warning(_("GPG error: %s: %s"), + Desc.Description.c_str(), + GPGError.c_str()); + } + // gpgv method failed + ReportMirrorFailureToCentral(*this, "GPGFailure", GPGError); + return false; +} + /*}}}*/ +// AcqMetaBase::Custom600Headers - Get header for AcqMetaBase /*{{{*/ +// --------------------------------------------------------------------- +string pkgAcqMetaBase::Custom600Headers() const +{ + std::string Header = pkgAcqTransactionItem::Custom600Headers(); + Header.append("\nIndex-File: true"); + std::string MaximumSize; + strprintf(MaximumSize, "\nMaximum-Size: %i", + _config->FindI("Acquire::MaxReleaseFileSize", 10*1000*1000)); + Header += MaximumSize; + + string const FinalFile = GetFinalFilename(); + struct stat Buf; + if (stat(FinalFile.c_str(),&Buf) == 0) + Header += "\nLast-Modified: " + TimeRFC1123(Buf.st_mtime, false); + + return Header; +} + /*}}}*/ +// AcqMetaBase::QueueForSignatureVerify /*{{{*/ +void pkgAcqMetaBase::QueueForSignatureVerify(pkgAcqTransactionItem * const I, std::string const &File, std::string const &Signature) +{ + AuthPass = true; + I->Desc.URI = "gpgv:" + Signature; + I->DestFile = File; + QueueURI(I->Desc); + I->SetActiveSubprocess("gpgv"); +} + /*}}}*/ +// AcqMetaBase::CheckDownloadDone /*{{{*/ +bool pkgAcqMetaBase::CheckDownloadDone(pkgAcqTransactionItem * const I, const std::string &Message, HashStringList const &Hashes) const +{ + // We have just finished downloading a Release file (it is not + // verified yet) + + // Save the final base URI we got this Release file from + if (I->UsedMirror.empty() == false && _config->FindB("Acquire::SameMirrorForAllIndexes", true)) + { + auto InReleasePath = Target.Option(IndexTarget::INRELEASE_PATH); + if (InReleasePath.empty()) + InReleasePath = "InRelease"; + + if (APT::String::Endswith(I->Desc.URI, InReleasePath)) + { + TransactionManager->BaseURI = I->Desc.URI.substr(0, I->Desc.URI.length() - InReleasePath.length()); + TransactionManager->UsedMirror = I->UsedMirror; + } + else if (APT::String::Endswith(I->Desc.URI, "Release")) + { + TransactionManager->BaseURI = I->Desc.URI.substr(0, I->Desc.URI.length() - strlen("Release")); + TransactionManager->UsedMirror = I->UsedMirror; + } + } + + std::string const FileName = LookupTag(Message,"Filename"); + if (FileName != I->DestFile && RealFileExists(I->DestFile) == false) + { + I->Local = true; + I->Desc.URI = "copy:" + FileName; + I->QueueURI(I->Desc); + return false; + } + + // make sure to verify against the right file on I-M-S hit + bool IMSHit = StringToBool(LookupTag(Message,"IMS-Hit"), false); + if (IMSHit == false && Hashes.usable()) + { + // detect IMS-Hits servers haven't detected by Hash comparison + std::string const FinalFile = I->GetFinalFilename(); + if (RealFileExists(FinalFile) && Hashes.VerifyFile(FinalFile) == true) + { + IMSHit = true; + RemoveFile("CheckDownloadDone", I->DestFile); + } + } + + if(IMSHit == true) + { + // for simplicity, the transaction manager is always InRelease + // even if it doesn't exist. + TransactionManager->IMSHit = true; + I->PartialFile = I->DestFile = I->GetFinalFilename(); + } + + // set Item to complete as the remaining work is all local (verify etc) + I->Complete = true; + + return true; +} + /*}}}*/ +bool pkgAcqMetaBase::CheckAuthDone(string const &Message, pkgAcquire::MethodConfig const *const Cnf) /*{{{*/ +{ + /* If we work with a recent version of our gpgv method, we expect that it tells us + which key(s) have signed the file so stuff like CVE-2018-0501 is harder in the future */ + if (Cnf->Version != "1.0" && LookupTag(Message, "Signed-By").empty()) + { + std::string errmsg; + strprintf(errmsg, "Internal Error: Signature on %s seems good, but expected details are missing! (%s)", Target.URI.c_str(), "Signed-By"); + if (ErrorText.empty()) + ErrorText = errmsg; + Status = StatAuthError; + return _error->Error("%s", errmsg.c_str()); + } + + // At this point, the gpgv method has succeeded, so there is a + // valid signature from a key in the trusted keyring. We + // perform additional verification of its contents, and use them + // to verify the indexes we are about to download + if (_config->FindB("Debug::pkgAcquire::Auth", false)) + std::cerr << "Signature verification succeeded: " << DestFile << std::endl; + + if (TransactionManager->IMSHit == false) + { + // open the last (In)Release if we have it + std::string const FinalFile = GetFinalFilename(); + std::string FinalRelease; + std::string FinalInRelease; + if (APT::String::Endswith(FinalFile, "InRelease")) + { + FinalInRelease = FinalFile; + FinalRelease = FinalFile.substr(0, FinalFile.length() - strlen("InRelease")) + "Release"; + } + else + { + FinalInRelease = FinalFile.substr(0, FinalFile.length() - strlen("Release")) + "InRelease"; + FinalRelease = FinalFile; + } + LoadLastMetaIndexParser(TransactionManager, FinalRelease, FinalInRelease); + } + + bool const GoodAuth = TransactionManager->MetaIndexParser->Load(DestFile, &ErrorText); + if (GoodAuth == false && AllowInsecureRepositories(InsecureType::WEAK, Target.Description, TransactionManager->MetaIndexParser, TransactionManager, this) == false) + { + Status = StatAuthError; + return false; + } + + if (!VerifyVendor(Message)) + { + Status = StatAuthError; + return false; + } + + // Download further indexes with verification + TransactionManager->QueueIndexes(GoodAuth); + + return GoodAuth; +} + /*}}}*/ +void pkgAcqMetaClearSig::QueueIndexes(bool const verify) /*{{{*/ +{ + // at this point the real Items are loaded in the fetcher + ExpectedAdditionalItems = 0; + + std::unordered_set<std::string> targetsSeen, componentsSeen; + bool const hasReleaseFile = TransactionManager->MetaIndexParser != NULL; + bool hasHashes = true; + auto IndexTargets = TransactionManager->MetaIndexParser->GetIndexTargets(); + if (hasReleaseFile && verify == false) + hasHashes = std::any_of(IndexTargets.begin(), IndexTargets.end(), + [&](IndexTarget const &Target) { return TransactionManager->MetaIndexParser->Exists(Target.MetaKey); }); + if (_config->FindB("Acquire::IndexTargets::Randomized", true) && likely(IndexTargets.empty() == false)) + { + /* For fallback handling and to have some reasonable progress information + we can't randomize everything, but at least the order in the same type + can be as we shouldn't be telling the mirrors (and everyone else watching) + which is native/foreign arch, specific order of preference of translations, … */ + auto range_start = IndexTargets.begin(); + auto seed = (std::chrono::high_resolution_clock::now().time_since_epoch() / std::chrono::nanoseconds(1)) ^ getpid(); + std::default_random_engine g(seed); + do { + auto const type = range_start->Option(IndexTarget::CREATED_BY); + auto const range_end = std::find_if_not(range_start, IndexTargets.end(), + [&type](IndexTarget const &T) { return type == T.Option(IndexTarget::CREATED_BY); }); + std::shuffle(range_start, range_end, g); + range_start = range_end; + } while (range_start != IndexTargets.end()); + } + /* Collect all components for which files exist to prevent apt from warning users + about "hidden" components for which not all files exist like main/debian-installer + and Translation files */ + if (hasReleaseFile == true) + for (auto const &Target : IndexTargets) + if (TransactionManager->MetaIndexParser->Exists(Target.MetaKey)) + { + auto component = Target.Option(IndexTarget::COMPONENT); + if (component.empty() == false) + componentsSeen.emplace(std::move(component)); + } + + for (auto&& Target: IndexTargets) + { + // if we have seen a target which is created-by a target this one here is declared a + // fallback to, we skip acquiring the fallback (but we make sure we clean up) + if (targetsSeen.find(Target.Option(IndexTarget::FALLBACK_OF)) != targetsSeen.end()) + { + targetsSeen.emplace(Target.Option(IndexTarget::CREATED_BY)); + new CleanupItem(Owner, TransactionManager, Target); + continue; + } + // all is an implementation detail. Users shouldn't use this as arch + // We need this support trickery here as e.g. Debian has binary-all files already, + // but arch:all packages are still in the arch:any files, so we would waste precious + // download time, bandwidth and diskspace for nothing, BUT Debian doesn't feature all + // in the set of supported architectures, so we can filter based on this property rather + // than invent an entirely new flag we would need to carry for all of eternity. + if (hasReleaseFile && Target.Option(IndexTarget::ARCHITECTURE) == "all") + { + if (TransactionManager->MetaIndexParser->IsArchitectureAllSupportedFor(Target) == false) + { + new CleanupItem(Owner, TransactionManager, Target); + continue; + } + } + + bool trypdiff = Target.OptionBool(IndexTarget::PDIFFS); + if (hasReleaseFile == true) + { + if (TransactionManager->MetaIndexParser->Exists(Target.MetaKey) == false) + { + auto const component = Target.Option(IndexTarget::COMPONENT); + if (component.empty() == false && + componentsSeen.find(component) == std::end(componentsSeen) && + TransactionManager->MetaIndexParser->HasSupportForComponent(component) == false) + { + new CleanupItem(Owner, TransactionManager, Target); + _error->Warning(_("Skipping acquire of configured file '%s' as repository '%s' doesn't have the component '%s' (component misspelt in sources.list?)"), + Target.MetaKey.c_str(), TransactionManager->Target.Description.c_str(), component.c_str()); + continue; + + } + + // optional targets that we do not have in the Release file are skipped + if (hasHashes == true && Target.IsOptional) + { + new CleanupItem(Owner, TransactionManager, Target); + continue; + } + + std::string const &arch = Target.Option(IndexTarget::ARCHITECTURE); + if (arch.empty() == false) + { + if (TransactionManager->MetaIndexParser->IsArchitectureSupported(arch) == false) + { + new CleanupItem(Owner, TransactionManager, Target); + _error->Notice(_("Skipping acquire of configured file '%s' as repository '%s' doesn't support architecture '%s'"), + Target.MetaKey.c_str(), TransactionManager->Target.Description.c_str(), arch.c_str()); + continue; + } + // if the architecture is officially supported but currently no packages for it available, + // ignore silently as this is pretty much the same as just shipping an empty file. + // if we don't know which architectures are supported, we do NOT ignore it to notify user about this + if (hasHashes == true && TransactionManager->MetaIndexParser->IsArchitectureSupported("*undefined*") == false) + { + new CleanupItem(Owner, TransactionManager, Target); + continue; + } + } + + if (hasHashes == true) + { + new CleanupItem(Owner, TransactionManager, Target); + _error->Warning(_("Skipping acquire of configured file '%s' as repository '%s' does not seem to provide it (sources.list entry misspelt?)"), + Target.MetaKey.c_str(), TransactionManager->Target.Description.c_str()); + continue; + } + else + { + new pkgAcqIndex(Owner, TransactionManager, Target); + continue; + } + } + else if (verify) + { + auto const hashes = GetExpectedHashesFor(Target.MetaKey); + if (hashes.empty() == false) + { + if (hashes.usable() == false && TargetIsAllowedToBe(TransactionManager->Target, InsecureType::WEAK) == false) + { + new CleanupItem(Owner, TransactionManager, Target); + _error->Warning(_("Skipping acquire of configured file '%s' as repository '%s' provides only weak security information for it"), + Target.MetaKey.c_str(), TransactionManager->Target.Description.c_str()); + continue; + } + // empty files are skipped as acquiring the very small compressed files is a waste of time + else if (hashes.FileSize() == 0) + { + new CleanupItem(Owner, TransactionManager, Target); + targetsSeen.emplace(Target.Option(IndexTarget::CREATED_BY)); + continue; + } + } + } + + // autoselect the compression method + std::vector<std::string> types = VectorizeString(Target.Option(IndexTarget::COMPRESSIONTYPES), ' '); + types.erase(std::remove_if(types.begin(), types.end(), [&](std::string const &t) { + if (t == "uncompressed") + return TransactionManager->MetaIndexParser->Exists(Target.MetaKey) == false; + std::string const MetaKey = Target.MetaKey + "." + t; + return TransactionManager->MetaIndexParser->Exists(MetaKey) == false; + }), types.end()); + if (types.empty() == false) + { + std::ostringstream os; + std::copy(types.begin(), types.end()-1, std::ostream_iterator<std::string>(os, " ")); + os << *types.rbegin(); + Target.Options["COMPRESSIONTYPES"] = os.str(); + } + else + Target.Options["COMPRESSIONTYPES"].clear(); + + std::string filename = GetExistingFilename(GetFinalFileNameFromURI(Target.URI)); + if (filename.empty() == false) + { + // if the Release file is a hit and we have an index it must be the current one + if (TransactionManager->IMSHit == true) + ; + else if (TransactionManager->LastMetaIndexParser != NULL) + { + // see if the file changed since the last Release file + // we use the uncompressed files as we might compress differently compared to the server, + // so the hashes might not match, even if they contain the same data. + HashStringList const newFile = GetExpectedHashesFromFor(TransactionManager->MetaIndexParser, Target.MetaKey); + HashStringList const oldFile = GetExpectedHashesFromFor(TransactionManager->LastMetaIndexParser, Target.MetaKey); + if (newFile != oldFile) + filename.clear(); + } + else + filename.clear(); + } + else + trypdiff = false; // no file to patch + + if (filename.empty() == false) + { + new NoActionItem(Owner, Target, filename); + std::string const idxfilename = GetFinalFileNameFromURI(GetDiffIndexURI(Target)); + if (FileExists(idxfilename)) + new NoActionItem(Owner, Target, idxfilename); + targetsSeen.emplace(Target.Option(IndexTarget::CREATED_BY)); + continue; + } + + // check if we have patches available + trypdiff &= TransactionManager->MetaIndexParser->Exists(GetDiffIndexFileName(Target.MetaKey)); + } + else + { + // if we have no file to patch, no point in trying + trypdiff &= (GetExistingFilename(GetFinalFileNameFromURI(Target.URI)).empty() == false); + } + + // no point in patching from local sources + if (trypdiff) + { + std::string const proto = Target.URI.substr(0, strlen("file:/")); + if (proto == "file:/" || proto == "copy:/" || proto == "cdrom:") + trypdiff = false; + } + + // Queue the Index file (Packages, Sources, Translation-$foo, …) + targetsSeen.emplace(Target.Option(IndexTarget::CREATED_BY)); + if (trypdiff) + new pkgAcqDiffIndex(Owner, TransactionManager, Target); + else + new pkgAcqIndex(Owner, TransactionManager, Target); + } +} + /*}}}*/ +bool pkgAcqMetaBase::VerifyVendor(string const &) /*{{{*/ +{ + if (TransactionManager->MetaIndexParser->GetValidUntil() > 0) + { + time_t const invalid_since = time(NULL) - TransactionManager->MetaIndexParser->GetValidUntil(); + if (invalid_since > 0) + { + std::string errmsg; + strprintf(errmsg, + // TRANSLATOR: The first %s is the URL of the bad Release file, the second is + // the time since then the file is invalid - formatted in the same way as in + // the download progress display (e.g. 7d 3h 42min 1s) + _("Release file for %s is expired (invalid since %s). " + "Updates for this repository will not be applied."), + Target.URI.c_str(), TimeToStr(invalid_since).c_str()); + if (ErrorText.empty()) + ErrorText = errmsg; + return _error->Error("%s", errmsg.c_str()); + } + } + + if (TransactionManager->MetaIndexParser->GetNotBefore() > 0) + { + time_t const invalid_for = TransactionManager->MetaIndexParser->GetNotBefore() - time(nullptr); + if (invalid_for > 0) + { + std::string errmsg; + strprintf(errmsg, + // TRANSLATOR: The first %s is the URL of the bad Release file, the second is + // the time until the file will be valid - formatted in the same way as in + // the download progress display (e.g. 7d 3h 42min 1s) + _("Release file for %s is not valid yet (invalid for another %s). " + "Updates for this repository will not be applied."), + Target.URI.c_str(), TimeToStr(invalid_for).c_str()); + if (ErrorText.empty()) + ErrorText = errmsg; + return _error->Error("%s", errmsg.c_str()); + } + } + + /* Did we get a file older than what we have? This is a last minute IMS hit and doubles + as a prevention of downgrading us to older (still valid) files */ + if (TransactionManager->IMSHit == false && TransactionManager->LastMetaIndexParser != NULL && + TransactionManager->LastMetaIndexParser->GetDate() > TransactionManager->MetaIndexParser->GetDate()) + { + TransactionManager->IMSHit = true; + RemoveFile("VerifyVendor", DestFile); + PartialFile = DestFile = GetFinalFilename(); + // load the 'old' file in the 'new' one instead of flipping pointers as + // the new one isn't owned by us, while the old one is so cleanup would be confused. + TransactionManager->MetaIndexParser->swapLoad(TransactionManager->LastMetaIndexParser); + delete TransactionManager->LastMetaIndexParser; + TransactionManager->LastMetaIndexParser = NULL; + } + + if (_config->FindB("Debug::pkgAcquire::Auth", false)) + { + std::cerr << "Got Codename: " << TransactionManager->MetaIndexParser->GetCodename() << std::endl; + std::cerr << "Got Suite: " << TransactionManager->MetaIndexParser->GetSuite() << std::endl; + std::cerr << "Expecting Dist: " << TransactionManager->MetaIndexParser->GetExpectedDist() << std::endl; + } + + // One day that might become fatal… + auto const ExpectedDist = TransactionManager->MetaIndexParser->GetExpectedDist(); + auto const NowCodename = TransactionManager->MetaIndexParser->GetCodename(); + if (TransactionManager->MetaIndexParser->CheckDist(ExpectedDist) == false) + _error->Warning(_("Conflicting distribution: %s (expected %s but got %s)"), + Desc.Description.c_str(), ExpectedDist.c_str(), NowCodename.c_str()); + + // changed info potentially breaks user config like pinning + if (TransactionManager->LastMetaIndexParser != nullptr) + { + std::vector<pkgAcquireStatus::ReleaseInfoChange> Changes; + auto const AllowInfoChange = _config->FindB("Acquire::AllowReleaseInfoChange", false); + auto const quietInfoChange = _config->FindB("quiet::ReleaseInfoChange", false); + struct { + char const * const Type; + bool const Allowed; + decltype(&metaIndex::GetOrigin) const Getter; + } checkers[] = { + { "Origin", AllowInfoChange, &metaIndex::GetOrigin }, + { "Label", AllowInfoChange, &metaIndex::GetLabel }, + { "Version", true, &metaIndex::GetVersion }, // numbers change all the time, that is okay + { "Suite", true, &metaIndex::GetSuite }, + { "Codename", AllowInfoChange, &metaIndex::GetCodename }, + { nullptr, false, nullptr } + }; + auto const CheckReleaseInfo = [&](char const * const Type, bool const AllowChange, decltype(checkers[0].Getter) const Getter) { + std::string const Last = (TransactionManager->LastMetaIndexParser->*Getter)(); + std::string const Now = (TransactionManager->MetaIndexParser->*Getter)(); + if (Last == Now) + return; + auto const Allow = _config->FindB(std::string("Acquire::AllowReleaseInfoChange::").append(Type), AllowChange); + if (Allow == true && _config->FindB(std::string("quiet::ReleaseInfoChange::").append(Type), quietInfoChange) == true) + return; + std::string msg; + strprintf(msg, _("Repository '%s' changed its '%s' value from '%s' to '%s'"), + Desc.Description.c_str(), Type, Last.c_str(), Now.c_str()); + Changes.push_back({Type, std::move(Last), std::move(Now), std::move(msg), Allow}); + }; + for (short i = 0; checkers[i].Type != nullptr; ++i) + CheckReleaseInfo(checkers[i].Type, checkers[i].Allowed, checkers[i].Getter); + + { + auto const Last = TransactionManager->LastMetaIndexParser->GetDefaultPin(); + auto const Now = TransactionManager->MetaIndexParser->GetDefaultPin(); + if (Last != Now) + { + auto const Allow = _config->FindB("Acquire::AllowReleaseInfoChange::DefaultPin", AllowInfoChange); + if (Allow == false || _config->FindB("quiet::ReleaseInfoChange::DefaultPin", quietInfoChange) == false) + { + std::string msg; + strprintf(msg, _("Repository '%s' changed its default priority for %s from %hi to %hi."), + Desc.Description.c_str(), "apt_preferences(5)", Last, Now); + Changes.push_back({"DefaultPin", std::to_string(Last), std::to_string(Now), std::move(msg), Allow}); + } + } + } + if (Changes.empty() == false) + { + auto const notes = TransactionManager->MetaIndexParser->GetReleaseNotes(); + if (notes.empty() == false) + { + std::string msg; + // TRANSLATOR: the "this" refers to changes in the repository like a new release or owner change + strprintf(msg, _("More information about this can be found online in the Release notes at: %s"), notes.c_str()); + Changes.push_back({"Release-Notes", "", std::move(notes), std::move(msg), true}); + } + if (std::any_of(Changes.begin(),Changes.end(),[](pkgAcquireStatus::ReleaseInfoChange const &c) { return c.DefaultAction == false; })) + { + std::string msg; + // TRANSLATOR: %s is the name of the manpage in question, e.g. apt-secure(8) + strprintf(msg, _("This must be accepted explicitly before updates for " + "this repository can be applied. See %s manpage for details."), "apt-secure(8)"); + Changes.push_back({"Confirmation", "", "", std::move(msg), true}); + } + + } + if (Owner->Log == nullptr) + return pkgAcquireStatus::ReleaseInfoChangesAsGlobalErrors(std::move(Changes)); + return Owner->Log->ReleaseInfoChanges(TransactionManager->LastMetaIndexParser, TransactionManager->MetaIndexParser, std::move(Changes)); + } + return true; +} + /*}}}*/ +pkgAcqMetaBase::~pkgAcqMetaBase() +{ +} + +pkgAcqMetaClearSig::pkgAcqMetaClearSig(pkgAcquire * const Owner, /*{{{*/ + IndexTarget const &ClearsignedTarget, + IndexTarget const &DetachedDataTarget, IndexTarget const &DetachedSigTarget, + metaIndex * const MetaIndexParser) : + pkgAcqMetaIndex(Owner, this, ClearsignedTarget, DetachedSigTarget), + d(NULL), DetachedDataTarget(DetachedDataTarget), + MetaIndexParser(MetaIndexParser), LastMetaIndexParser(NULL) +{ + // index targets + (worst case:) Release/Release.gpg + ExpectedAdditionalItems = std::numeric_limits<decltype(ExpectedAdditionalItems)>::max(); + TransactionManager->Add(this); +} + /*}}}*/ +pkgAcqMetaClearSig::~pkgAcqMetaClearSig() /*{{{*/ +{ + if (LastMetaIndexParser != NULL) + delete LastMetaIndexParser; +} + /*}}}*/ +// pkgAcqMetaClearSig::Custom600Headers - Insert custom request headers /*{{{*/ +string pkgAcqMetaClearSig::Custom600Headers() const +{ + string Header = pkgAcqMetaBase::Custom600Headers(); + Header += "\nFail-Ignore: true"; + std::string const key = TransactionManager->MetaIndexParser->GetSignedBy(); + if (key.empty() == false) + Header += "\nSigned-By: " + key; + + return Header; +} + /*}}}*/ +void pkgAcqMetaClearSig::Finished() /*{{{*/ +{ + if(_config->FindB("Debug::Acquire::Transaction", false) == true) + std::clog << "Finished: " << DestFile <<std::endl; + if(TransactionManager->State == TransactionStarted && + TransactionManager->TransactionHasError() == false) + TransactionManager->CommitTransaction(); +} + /*}}}*/ +bool pkgAcqMetaClearSig::VerifyDone(std::string const &Message, /*{{{*/ + pkgAcquire::MethodConfig const * const Cnf) +{ + if (Item::VerifyDone(Message, Cnf) == false) + return false; + + if (FileExists(DestFile) && !StartsWithGPGClearTextSignature(DestFile)) + return RenameOnError(NotClearsigned); + + return true; +} + /*}}}*/ +// pkgAcqMetaClearSig::Done - We got a file /*{{{*/ +void pkgAcqMetaClearSig::Done(std::string const &Message, + HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cnf) +{ + Item::Done(Message, Hashes, Cnf); + + if(AuthPass == false) + { + if(CheckDownloadDone(this, Message, Hashes) == true) + QueueForSignatureVerify(this, DestFile, DestFile); + return; + } + else if (CheckAuthDone(Message, Cnf) == true) + { + if (TransactionManager->IMSHit == false) + TransactionManager->TransactionStageCopy(this, DestFile, GetFinalFilename()); + else if (RealFileExists(GetFinalFilename()) == false) + { + // We got an InRelease file IMSHit, but we haven't one, which means + // we had a valid Release/Release.gpg combo stepping in, which we have + // to 'acquire' now to ensure list cleanup isn't removing them + new NoActionItem(Owner, DetachedDataTarget); + new NoActionItem(Owner, DetachedSigTarget); + } + } + else if (Status != StatAuthError) + { + string const FinalFile = GetFinalFileNameFromURI(DetachedDataTarget.URI); + string const OldFile = GetFinalFilename(); + if (TransactionManager->IMSHit == false) + TransactionManager->TransactionStageCopy(this, DestFile, FinalFile); + else if (RealFileExists(OldFile) == false) + new NoActionItem(Owner, DetachedDataTarget); + else + TransactionManager->TransactionStageCopy(this, OldFile, FinalFile); + } +} + /*}}}*/ +void pkgAcqMetaClearSig::Failed(string const &Message,pkgAcquire::MethodConfig const * const Cnf) /*{{{*/ +{ + Item::Failed(Message, Cnf); + + if (AuthPass == false) + { + if (Status == StatTransientNetworkError) + { + TransactionManager->AbortTransaction(); + return; + } + auto const failreason = LookupTag(Message, "FailReason"); + auto const httperror = "HttpError"; + if (Status == StatAuthError || + Target.Option(IndexTarget::INRELEASE_PATH).empty() == false || /* do not fallback if InRelease was requested */ + (strncmp(failreason.c_str(), httperror, strlen(httperror)) == 0 && + failreason != "HttpError404")) + { + // if we expected a ClearTextSignature (InRelease) but got a network + // error or got a file, but it wasn't valid, we end up here (see VerifyDone). + // As these is usually called by web-portals we do not try Release/Release.gpg + // as this is going to fail anyway and instead abort our try (LP#346386) + _error->PushToStack(); + _error->Error(_("Failed to fetch %s %s"), Target.URI.c_str(), ErrorText.c_str()); + if (Target.Option(IndexTarget::INRELEASE_PATH).empty() == true && AllowInsecureRepositories(InsecureType::UNSIGNED, Target.Description, TransactionManager->MetaIndexParser, TransactionManager, this) == true) + _error->RevertToStack(); + else + return; + } + + // Queue the 'old' InRelease file for removal if we try Release.gpg + // as otherwise the file will stay around and gives a false-auth + // impression (CVE-2012-0214) + TransactionManager->TransactionStageRemoval(this, GetFinalFilename()); + Status = StatDone; + + new pkgAcqMetaIndex(Owner, TransactionManager, DetachedDataTarget, DetachedSigTarget); + } + else + { + if(CheckStopAuthentication(this, Message)) + return; + + if(AllowInsecureRepositories(InsecureType::UNSIGNED, Target.Description, TransactionManager->MetaIndexParser, TransactionManager, this) == true) + { + Status = StatDone; + + /* InRelease files become Release files, otherwise + * they would be considered as trusted later on */ + string const FinalRelease = GetFinalFileNameFromURI(DetachedDataTarget.URI); + string const PartialRelease = GetPartialFileNameFromURI(DetachedDataTarget.URI); + string const FinalReleasegpg = GetFinalFileNameFromURI(DetachedSigTarget.URI); + string const FinalInRelease = GetFinalFilename(); + Rename(DestFile, PartialRelease); + TransactionManager->TransactionStageCopy(this, PartialRelease, FinalRelease); + LoadLastMetaIndexParser(TransactionManager, FinalRelease, FinalInRelease); + + // we parse the indexes here because at this point the user wanted + // a repository that may potentially harm him + if (TransactionManager->MetaIndexParser->Load(PartialRelease, &ErrorText) == false || VerifyVendor(Message) == false) + /* expired Release files are still a problem you need extra force for */; + else + TransactionManager->QueueIndexes(true); + } + } +} + /*}}}*/ + +pkgAcqMetaIndex::pkgAcqMetaIndex(pkgAcquire * const Owner, /*{{{*/ + pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &DataTarget, + IndexTarget const &DetachedSigTarget) : + pkgAcqMetaBase(Owner, TransactionManager, DataTarget), d(NULL), + DetachedSigTarget(DetachedSigTarget) +{ + if(_config->FindB("Debug::Acquire::Transaction", false) == true) + std::clog << "New pkgAcqMetaIndex with TransactionManager " + << this->TransactionManager << std::endl; + + DestFile = GetPartialFileNameFromURI(DataTarget.URI); + + // Create the item + Desc.Description = DataTarget.Description; + Desc.Owner = this; + Desc.ShortDesc = DataTarget.ShortDesc; + + // Rewrite the description URI if INRELEASE_PATH was specified so + // we download the specified file instead. + auto InReleasePath = DataTarget.Option(IndexTarget::INRELEASE_PATH); + if (InReleasePath.empty() == false && APT::String::Endswith(DataTarget.URI, "/InRelease")) + { + Desc.URI = DataTarget.URI.substr(0, DataTarget.URI.size() - strlen("InRelease")) + InReleasePath; + } + else + { + Desc.URI = DataTarget.URI; + } + + QueueURI(Desc); +} + /*}}}*/ +void pkgAcqMetaIndex::Done(string const &Message, /*{{{*/ + HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cfg) +{ + Item::Done(Message,Hashes,Cfg); + + if(CheckDownloadDone(this, Message, Hashes)) + { + // we have a Release file, now download the Signature, all further + // verify/queue for additional downloads will be done in the + // pkgAcqMetaSig::Done() code + new pkgAcqMetaSig(Owner, TransactionManager, DetachedSigTarget, this); + } +} + /*}}}*/ +// pkgAcqMetaIndex::Failed - no Release file present /*{{{*/ +void pkgAcqMetaIndex::Failed(string const &Message, + pkgAcquire::MethodConfig const * const Cnf) +{ + pkgAcquire::Item::Failed(Message, Cnf); + Status = StatDone; + + // No Release file was present so fall + // back to queueing Packages files without verification + // only allow going further if the user explicitly wants it + if(AllowInsecureRepositories(InsecureType::NORELEASE, Target.Description, TransactionManager->MetaIndexParser, TransactionManager, this) == true) + { + // ensure old Release files are removed + TransactionManager->TransactionStageRemoval(this, GetFinalFilename()); + + // queue without any kind of hashsum support + TransactionManager->QueueIndexes(false); + } +} + /*}}}*/ +std::string pkgAcqMetaIndex::DescURI() const /*{{{*/ +{ + return Target.URI; +} + /*}}}*/ +pkgAcqMetaIndex::~pkgAcqMetaIndex() {} + +// AcqMetaSig::AcqMetaSig - Constructor /*{{{*/ +pkgAcqMetaSig::pkgAcqMetaSig(pkgAcquire * const Owner, + pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &Target, + pkgAcqMetaIndex * const MetaIndex) : + pkgAcqTransactionItem(Owner, TransactionManager, Target), d(NULL), MetaIndex(MetaIndex) +{ + DestFile = GetPartialFileNameFromURI(Target.URI); + + // remove any partial downloaded sig-file in partial/. + // it may confuse proxies and is too small to warrant a + // partial download anyway + RemoveFile("pkgAcqMetaSig", DestFile); + + // set the TransactionManager + if(_config->FindB("Debug::Acquire::Transaction", false) == true) + std::clog << "New pkgAcqMetaSig with TransactionManager " + << TransactionManager << std::endl; + + // Create the item + Desc.Description = Target.Description; + Desc.Owner = this; + Desc.ShortDesc = Target.ShortDesc; + Desc.URI = Target.URI; + + // If we got a hit for Release, we will get one for Release.gpg too (or obscure errors), + // so we skip the download step and go instantly to verification + if (TransactionManager->IMSHit == true && RealFileExists(GetFinalFilename())) + { + Complete = true; + Status = StatDone; + PartialFile = DestFile = GetFinalFilename(); + MetaIndexFileSignature = DestFile; + MetaIndex->QueueForSignatureVerify(this, MetaIndex->DestFile, DestFile); + } + else + QueueURI(Desc); +} + /*}}}*/ +pkgAcqMetaSig::~pkgAcqMetaSig() /*{{{*/ +{ +} + /*}}}*/ +// pkgAcqMetaSig::Custom600Headers - Insert custom request headers /*{{{*/ +std::string pkgAcqMetaSig::Custom600Headers() const +{ + std::string Header = pkgAcqTransactionItem::Custom600Headers(); + std::string const key = TransactionManager->MetaIndexParser->GetSignedBy(); + if (key.empty() == false) + Header += "\nSigned-By: " + key; + return Header; +} + /*}}}*/ +// AcqMetaSig::Done - The signature was downloaded/verified /*{{{*/ +void pkgAcqMetaSig::Done(string const &Message, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cfg) +{ + if (MetaIndexFileSignature.empty() == false) + { + DestFile = MetaIndexFileSignature; + MetaIndexFileSignature.clear(); + } + Item::Done(Message, Hashes, Cfg); + + if(MetaIndex->AuthPass == false) + { + if(MetaIndex->CheckDownloadDone(this, Message, Hashes) == true) + { + // destfile will be modified to point to MetaIndexFile for the + // gpgv method, so we need to save it here + MetaIndexFileSignature = DestFile; + MetaIndex->QueueForSignatureVerify(this, MetaIndex->DestFile, DestFile); + } + return; + } + else if (MetaIndex->CheckAuthDone(Message, Cfg) == true) + { + auto const Releasegpg = GetFinalFilename(); + auto const Release = MetaIndex->GetFinalFilename(); + // if this is an IMS-Hit on Release ensure we also have the Release.gpg file stored + // (previously an unknown pubkey) – but only if the Release file exists locally (unlikely + // event of InRelease removed from the mirror causing fallback but still an IMS-Hit) + if (TransactionManager->IMSHit == false || + (FileExists(Releasegpg) == false && FileExists(Release) == true)) + { + TransactionManager->TransactionStageCopy(this, DestFile, Releasegpg); + TransactionManager->TransactionStageCopy(MetaIndex, MetaIndex->DestFile, Release); + } + } + else if (MetaIndex->Status != StatAuthError) + { + std::string const FinalFile = MetaIndex->GetFinalFilename(); + if (TransactionManager->IMSHit == false) + TransactionManager->TransactionStageCopy(MetaIndex, MetaIndex->DestFile, FinalFile); + else + TransactionManager->TransactionStageCopy(MetaIndex, FinalFile, FinalFile); + } +} + /*}}}*/ +void pkgAcqMetaSig::Failed(string const &Message,pkgAcquire::MethodConfig const * const Cnf)/*{{{*/ +{ + Item::Failed(Message,Cnf); + + // check if we need to fail at this point + if (MetaIndex->AuthPass == true && MetaIndex->CheckStopAuthentication(this, Message)) + return; + + // ensures that a Release.gpg file in the lists/ is removed by the transaction + TransactionManager->TransactionStageRemoval(this, DestFile); + + // only allow going further if the user explicitly wants it + if (AllowInsecureRepositories(InsecureType::UNSIGNED, MetaIndex->Target.Description, TransactionManager->MetaIndexParser, TransactionManager, this) == true) + { + string const FinalRelease = MetaIndex->GetFinalFilename(); + string const FinalInRelease = TransactionManager->GetFinalFilename(); + LoadLastMetaIndexParser(TransactionManager, FinalRelease, FinalInRelease); + + // we parse the indexes here because at this point the user wanted + // a repository that may potentially harm him + bool const GoodLoad = TransactionManager->MetaIndexParser->Load(MetaIndex->DestFile, &ErrorText); + if (MetaIndex->VerifyVendor(Message) == false) + /* expired Release files are still a problem you need extra force for */; + else + TransactionManager->QueueIndexes(GoodLoad); + + TransactionManager->TransactionStageCopy(MetaIndex, MetaIndex->DestFile, FinalRelease); + } + else if (TransactionManager->IMSHit == false) + Rename(MetaIndex->DestFile, MetaIndex->DestFile + ".FAILED"); + + // FIXME: this is used often (e.g. in pkgAcqIndexTrans) so refactor + if (Cnf->LocalOnly == true || + StringToBool(LookupTag(Message,"Transient-Failure"),false) == false) + { + // Ignore this + Status = StatDone; + } +} + /*}}}*/ + + +// AcqBaseIndex - Constructor /*{{{*/ +pkgAcqBaseIndex::pkgAcqBaseIndex(pkgAcquire * const Owner, + pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &Target) +: pkgAcqTransactionItem(Owner, TransactionManager, Target), d(NULL) +{ +} + /*}}}*/ +void pkgAcqBaseIndex::Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf)/*{{{*/ +{ + pkgAcquire::Item::Failed(Message, Cnf); + if (Status != StatAuthError) + return; + + ErrorText.append("Release file created at: "); + auto const timespec = TransactionManager->MetaIndexParser->GetDate(); + if (timespec == 0) + ErrorText.append("<unknown>"); + else + ErrorText.append(TimeRFC1123(timespec, true)); + ErrorText.append("\n"); +} + /*}}}*/ +pkgAcqBaseIndex::~pkgAcqBaseIndex() {} + +// AcqDiffIndex::AcqDiffIndex - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* Get the DiffIndex file first and see if there are patches available + * If so, create a pkgAcqIndexDiffs fetcher that will get and apply the + * patches. If anything goes wrong in that process, it will fall back to + * the original packages file + */ +pkgAcqDiffIndex::pkgAcqDiffIndex(pkgAcquire * const Owner, + pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &Target) + : pkgAcqIndex(Owner, TransactionManager, Target, true), d(NULL), diffs(NULL) +{ + // FIXME: Magic number as an upper bound on pdiffs we will reasonably acquire + ExpectedAdditionalItems = 40; + Debug = _config->FindB("Debug::pkgAcquire::Diffs",false); + + CompressionExtensions.clear(); + { + std::vector<std::string> types = APT::Configuration::getCompressionTypes(); + if (types.empty() == false) + { + std::ostringstream os; + std::copy_if(types.begin(), types.end()-1, std::ostream_iterator<std::string>(os, " "), [&](std::string const type) { + if (type == "uncompressed") + return true; + return TransactionManager->MetaIndexParser->Exists(GetDiffIndexFileName(Target.MetaKey) + '.' + type); + }); + os << *types.rbegin(); + CompressionExtensions = os.str(); + } + } + Init(GetDiffIndexURI(Target), GetDiffIndexFileName(Target.Description), Target.ShortDesc); + + if(Debug) + std::clog << "pkgAcqDiffIndex: " << Desc.URI << std::endl; +} + /*}}}*/ +void pkgAcqDiffIndex::QueueOnIMSHit() const /*{{{*/ +{ + // list cleanup needs to know that this file as well as the already + // present index is ours, so we create an empty diff to save it for us + new pkgAcqIndexDiffs(Owner, TransactionManager, Target); +} + /*}}}*/ +static bool RemoveFileForBootstrapLinking(std::string &ErrorText, std::string const &For, std::string const &Boot)/*{{{*/ +{ + if (FileExists(Boot) && RemoveFile("Bootstrap-linking", Boot) == false) + { + strprintf(ErrorText, "Bootstrap for patching %s by removing stale %s failed!", For.c_str(), Boot.c_str()); + return false; + } + return true; +} + /*}}}*/ +bool pkgAcqDiffIndex::ParseDiffIndex(string const &IndexDiffFile) /*{{{*/ +{ + available_patches.clear(); + ExpectedAdditionalItems = 0; + // failing here is fine: our caller will take care of trying to + // get the complete file if patching fails + if(Debug) + std::clog << "pkgAcqDiffIndex::ParseIndexDiff() " << IndexDiffFile + << std::endl; + + FileFd Fd(IndexDiffFile, FileFd::ReadOnly, FileFd::Extension); + pkgTagFile TF(&Fd); + if (Fd.IsOpen() == false || Fd.Failed()) + return false; + + pkgTagSection Tags; + if(unlikely(TF.Step(Tags) == false)) + return false; + + HashStringList ServerHashes; + unsigned long long ServerSize = 0; + + auto const &posix = std::locale::classic(); + for (char const * const * type = HashString::SupportedHashes(); *type != NULL; ++type) + { + std::string tagname = *type; + tagname.append("-Current"); + std::string const tmp = Tags.FindS(tagname.c_str()); + if (tmp.empty() == true) + continue; + + string hash; + unsigned long long size; + std::stringstream ss(tmp); + ss.imbue(posix); + ss >> hash >> size; + if (unlikely(hash.empty() == true)) + continue; + if (unlikely(ServerSize != 0 && ServerSize != size)) + continue; + ServerHashes.push_back(HashString(*type, hash)); + ServerSize = size; + } + + if (ServerHashes.usable() == false) + { + ErrorText = "Did not find a good hashsum in the index"; + return false; + } + + std::string const CurrentPackagesFile = GetFinalFileNameFromURI(Target.URI); + HashStringList const TargetFileHashes = GetExpectedHashesFor(Target.MetaKey); + if (TargetFileHashes.usable() == false || ServerHashes != TargetFileHashes) + { + ErrorText = "Index has different hashes than parser (probably older)"; + return false; + } + + HashStringList LocalHashes; + // try avoiding calculating the hash here as this is costly + if (TransactionManager->LastMetaIndexParser != NULL) + LocalHashes = GetExpectedHashesFromFor(TransactionManager->LastMetaIndexParser, Target.MetaKey); + if (LocalHashes.usable() == false) + { + FileFd fd(CurrentPackagesFile, FileFd::ReadOnly, FileFd::Auto); + Hashes LocalHashesCalc(ServerHashes); + LocalHashesCalc.AddFD(fd); + LocalHashes = LocalHashesCalc.GetHashStringList(); + } + + if (ServerHashes == LocalHashes) + { + available_patches.clear(); + return true; + } + + if(Debug) + std::clog << "Server-Current: " << ServerHashes.find(NULL)->toStr() << " and we start at " + << CurrentPackagesFile << " " << LocalHashes.FileSize() << " " << LocalHashes.find(NULL)->toStr() << std::endl; + + // historically, older hashes have more info than newer ones, so start + // collecting with older ones first to avoid implementing complicated + // information merging techniques… a failure is after all always + // recoverable with a complete file and hashes aren't changed that often. + std::vector<char const *> types; + for (char const * const * type = HashString::SupportedHashes(); *type != NULL; ++type) + types.push_back(*type); + + // parse all of (provided) history + bool firstAcceptedHashes = true; + for (auto type = types.crbegin(); type != types.crend(); ++type) + { + if (LocalHashes.find(*type) == NULL) + continue; + + std::string tagname = *type; + tagname.append("-History"); + std::string const tmp = Tags.FindS(tagname.c_str()); + if (tmp.empty() == true) + continue; + + string hash, filename; + unsigned long long size; + std::stringstream ss(tmp); + ss.imbue(posix); + + while (ss >> hash >> size >> filename) + { + if (unlikely(hash.empty() == true || filename.empty() == true)) + continue; + + // see if we have a record for this file already + std::vector<DiffInfo>::iterator cur = available_patches.begin(); + for (; cur != available_patches.end(); ++cur) + { + if (cur->file != filename) + continue; + cur->result_hashes.push_back(HashString(*type, hash)); + break; + } + if (cur != available_patches.end()) + continue; + if (firstAcceptedHashes == true) + { + DiffInfo next; + next.file = filename; + next.result_hashes.push_back(HashString(*type, hash)); + next.result_hashes.FileSize(size); + available_patches.push_back(next); + } + else + { + if (Debug == true) + std::clog << "pkgAcqDiffIndex: " << IndexDiffFile << ": File " << filename + << " wasn't in the list for the first parsed hash! (history)" << std::endl; + break; + } + } + firstAcceptedHashes = false; + } + + if (unlikely(available_patches.empty() == true)) + { + ErrorText = "Couldn't find any patches for the patch series"; + return false; + } + + for (auto type = types.crbegin(); type != types.crend(); ++type) + { + if (LocalHashes.find(*type) == NULL) + continue; + + std::string tagname = *type; + tagname.append("-Patches"); + std::string const tmp = Tags.FindS(tagname.c_str()); + if (tmp.empty() == true) + continue; + + string hash, filename; + unsigned long long size; + std::stringstream ss(tmp); + ss.imbue(posix); + + while (ss >> hash >> size >> filename) + { + if (unlikely(hash.empty() == true || filename.empty() == true)) + continue; + + // see if we have a record for this file already + std::vector<DiffInfo>::iterator cur = available_patches.begin(); + for (; cur != available_patches.end(); ++cur) + { + if (cur->file != filename) + continue; + if (cur->patch_hashes.empty()) + cur->patch_hashes.FileSize(size); + cur->patch_hashes.push_back(HashString(*type, hash)); + break; + } + if (cur != available_patches.end()) + continue; + if (Debug == true) + std::clog << "pkgAcqDiffIndex: " << IndexDiffFile << ": File " << filename + << " wasn't in the list for the first parsed hash! (patches)" << std::endl; + break; + } + } + + for (auto type = types.crbegin(); type != types.crend(); ++type) + { + std::string tagname = *type; + tagname.append("-Download"); + std::string const tmp = Tags.FindS(tagname.c_str()); + if (tmp.empty() == true) + continue; + + string hash, filename; + unsigned long long size; + std::stringstream ss(tmp); + ss.imbue(posix); + + // FIXME: all of pdiff supports only .gz compressed patches + while (ss >> hash >> size >> filename) + { + if (unlikely(hash.empty() == true || filename.empty() == true)) + continue; + if (unlikely(APT::String::Endswith(filename, ".gz") == false)) + continue; + filename.erase(filename.length() - 3); + + // see if we have a record for this file already + std::vector<DiffInfo>::iterator cur = available_patches.begin(); + for (; cur != available_patches.end(); ++cur) + { + if (cur->file != filename) + continue; + if (cur->download_hashes.empty()) + cur->download_hashes.FileSize(size); + cur->download_hashes.push_back(HashString(*type, hash)); + break; + } + if (cur != available_patches.end()) + continue; + if (Debug == true) + std::clog << "pkgAcqDiffIndex: " << IndexDiffFile << ": File " << filename + << " wasn't in the list for the first parsed hash! (download)" << std::endl; + break; + } + } + + + bool foundStart = false; + for (std::vector<DiffInfo>::iterator cur = available_patches.begin(); + cur != available_patches.end(); ++cur) + { + if (LocalHashes != cur->result_hashes) + continue; + + available_patches.erase(available_patches.begin(), cur); + foundStart = true; + break; + } + + if (foundStart == false || unlikely(available_patches.empty() == true)) + { + ErrorText = "Couldn't find the start of the patch series"; + return false; + } + + for (auto const &patch: available_patches) + if (patch.result_hashes.usable() == false || + patch.patch_hashes.usable() == false || + patch.download_hashes.usable() == false) + { + strprintf(ErrorText, "Provides no usable hashes for %s", patch.file.c_str()); + return false; + } + + // patching with too many files is rather slow compared to a fast download + unsigned long const fileLimit = _config->FindI("Acquire::PDiffs::FileLimit", 0); + if (fileLimit != 0 && fileLimit < available_patches.size()) + { + strprintf(ErrorText, "Need %lu diffs, but limit is %lu", available_patches.size(), fileLimit); + return false; + } + + // calculate the size of all patches we have to get + unsigned short const sizeLimitPercent = _config->FindI("Acquire::PDiffs::SizeLimit", 100); + if (sizeLimitPercent > 0) + { + unsigned long long downloadSize = std::accumulate(available_patches.begin(), + available_patches.end(), 0llu, [](unsigned long long const T, DiffInfo const &I) { + return T + I.download_hashes.FileSize(); + }); + if (downloadSize != 0) + { + unsigned long long downloadSizeIdx = 0; + auto const types = VectorizeString(Target.Option(IndexTarget::COMPRESSIONTYPES), ' '); + for (auto const &t : types) + { + std::string MetaKey = Target.MetaKey; + if (t != "uncompressed") + MetaKey += '.' + t; + HashStringList const hsl = GetExpectedHashesFor(MetaKey); + if (unlikely(hsl.usable() == false)) + continue; + downloadSizeIdx = hsl.FileSize(); + break; + } + unsigned long long const sizeLimit = downloadSizeIdx * sizeLimitPercent; + if ((sizeLimit/100) < downloadSize) + { + strprintf(ErrorText, "Need %llu compressed bytes, but limit is %llu and original is %llu", downloadSize, (sizeLimit/100), downloadSizeIdx); + return false; + } + } + } + + /* decide if we should download patches one by one or in one go: + The first is good if the server merges patches, but many don't so client + based merging can be attempt in which case the second is better. + "bad things" will happen if patches are merged on the server, + but client side merging is attempt as well */ + pdiff_merge = _config->FindB("Acquire::PDiffs::Merge", true); + if (pdiff_merge == true) + { + // reprepro adds this flag if it has merged patches on the server + std::string const precedence = Tags.FindS("X-Patch-Precedence"); + pdiff_merge = (precedence != "merged"); + } + + // clean the plate + { + std::string const Final = GetExistingFilename(CurrentPackagesFile); + if (unlikely(Final.empty())) // because we wouldn't be called in such a case + return false; + std::string const PartialFile = GetPartialFileNameFromURI(Target.URI); + std::string const PatchedFile = GetKeepCompressedFileName(PartialFile + "-patched", Target); + if (RemoveFileForBootstrapLinking(ErrorText, CurrentPackagesFile, PartialFile) == false || + RemoveFileForBootstrapLinking(ErrorText, CurrentPackagesFile, PatchedFile) == false) + return false; + for (auto const &ext : APT::Configuration::getCompressorExtensions()) + { + if (RemoveFileForBootstrapLinking(ErrorText, CurrentPackagesFile, PartialFile + ext) == false || + RemoveFileForBootstrapLinking(ErrorText, CurrentPackagesFile, PatchedFile + ext) == false) + return false; + } + std::string const Ext = Final.substr(CurrentPackagesFile.length()); + std::string const Partial = PartialFile + Ext; + if (symlink(Final.c_str(), Partial.c_str()) != 0) + { + strprintf(ErrorText, "Bootstrap for patching by linking %s to %s failed!", Final.c_str(), Partial.c_str()); + return false; + } + } + + return true; +} + /*}}}*/ +void pkgAcqDiffIndex::Failed(string const &Message,pkgAcquire::MethodConfig const * const Cnf)/*{{{*/ +{ + if (CommonFailed(GetDiffIndexURI(Target), Message, Cnf)) + return; + + RenameOnError(PDiffError); + Status = StatDone; + ExpectedAdditionalItems = 0; + + if(Debug) + std::clog << "pkgAcqDiffIndex failed: " << Desc.URI << " with " << Message << std::endl + << "Falling back to normal index file acquire" << std::endl; + + new pkgAcqIndex(Owner, TransactionManager, Target); +} + /*}}}*/ +bool pkgAcqDiffIndex::VerifyDone(std::string const &Message, pkgAcquire::MethodConfig const * const)/*{{{*/ +{ + string const FinalFile = GetFinalFilename(); + if(StringToBool(LookupTag(Message,"IMS-Hit"),false)) + DestFile = FinalFile; + + if (ParseDiffIndex(DestFile)) + return true; + + Status = StatError; + if (ErrorText.empty()) + ErrorText = "Couldn't parse pdiff index"; + return false; +} + /*}}}*/ +void pkgAcqDiffIndex::Done(string const &Message,HashStringList const &Hashes, /*{{{*/ + pkgAcquire::MethodConfig const * const Cnf) +{ + if(Debug) + std::clog << "pkgAcqDiffIndex::Done(): " << Desc.URI << std::endl; + + Item::Done(Message, Hashes, Cnf); + + if (available_patches.empty()) + { + // we have the same sha1 as the server so we are done here + if(Debug) + std::clog << "pkgAcqDiffIndex: Package file is up-to-date" << std::endl; + QueueOnIMSHit(); + } + else + { + if (pdiff_merge == false) + new pkgAcqIndexDiffs(Owner, TransactionManager, Target, available_patches); + else + { + diffs = new std::vector<pkgAcqIndexMergeDiffs*>(available_patches.size()); + for(size_t i = 0; i < available_patches.size(); ++i) + (*diffs)[i] = new pkgAcqIndexMergeDiffs(Owner, TransactionManager, + Target, + available_patches[i], + diffs); + } + } + + TransactionManager->TransactionStageCopy(this, DestFile, GetFinalFilename()); + + Complete = true; + Status = StatDone; + Dequeue(); + + return; +} + /*}}}*/ +pkgAcqDiffIndex::~pkgAcqDiffIndex() +{ + if (diffs != NULL) + delete diffs; +} + +// AcqIndexDiffs::AcqIndexDiffs - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* The package diff is added to the queue. one object is constructed + * for each diff and the index + */ +pkgAcqIndexDiffs::pkgAcqIndexDiffs(pkgAcquire *const Owner, + pkgAcqMetaClearSig *const TransactionManager, + IndexTarget const &Target, + vector<DiffInfo> const &diffs) + : pkgAcqBaseIndex(Owner, TransactionManager, Target), + available_patches(diffs) +{ + DestFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI), Target); + + Debug = _config->FindB("Debug::pkgAcquire::Diffs",false); + + Desc.Owner = this; + Desc.ShortDesc = Target.ShortDesc; + + if(available_patches.empty() == true) + { + // we are done (yeah!), check hashes against the final file + DestFile = GetKeepCompressedFileName(GetFinalFileNameFromURI(Target.URI), Target); + Finish(true); + } + else + { + State = StateFetchDiff; + QueueNextDiff(); + } +} + /*}}}*/ +void pkgAcqIndexDiffs::Failed(string const &Message,pkgAcquire::MethodConfig const * const Cnf)/*{{{*/ +{ + pkgAcqBaseIndex::Failed(Message,Cnf); + Status = StatDone; + + DestFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI), Target); + if(Debug) + std::clog << "pkgAcqIndexDiffs failed: " << Desc.URI << " with " << Message << std::endl + << "Falling back to normal index file acquire " << std::endl; + RenameOnError(PDiffError); + std::string const patchname = GetDiffsPatchFileName(DestFile); + if (RealFileExists(patchname)) + Rename(patchname, patchname + ".FAILED"); + std::string const UnpatchedFile = GetExistingFilename(GetPartialFileNameFromURI(Target.URI)); + if (UnpatchedFile.empty() == false && FileExists(UnpatchedFile)) + Rename(UnpatchedFile, UnpatchedFile + ".FAILED"); + new pkgAcqIndex(Owner, TransactionManager, Target); + Finish(); +} + /*}}}*/ +// Finish - helper that cleans the item out of the fetcher queue /*{{{*/ +void pkgAcqIndexDiffs::Finish(bool allDone) +{ + if(Debug) + std::clog << "pkgAcqIndexDiffs::Finish(): " + << allDone << " " + << Desc.URI << std::endl; + + // we restore the original name, this is required, otherwise + // the file will be cleaned + if(allDone) + { + std::string const Final = GetKeepCompressedFileName(GetFinalFilename(), Target); + TransactionManager->TransactionStageCopy(this, DestFile, Final); + + // this is for the "real" finish + Complete = true; + Status = StatDone; + Dequeue(); + if(Debug) + std::clog << "\n\nallDone: " << DestFile << "\n" << std::endl; + return; + } + else + DestFile.clear(); + + if(Debug) + std::clog << "Finishing: " << Desc.URI << std::endl; + Complete = false; + Status = StatDone; + Dequeue(); + return; +} + /*}}}*/ +bool pkgAcqIndexDiffs::QueueNextDiff() /*{{{*/ +{ + // calc sha1 of the just patched file + std::string const PartialFile = GetExistingFilename(GetPartialFileNameFromURI(Target.URI)); + if(unlikely(PartialFile.empty())) + { + Failed("Message: The file " + GetPartialFileNameFromURI(Target.URI) + " isn't available", NULL); + return false; + } + + FileFd fd(PartialFile, FileFd::ReadOnly, FileFd::Extension); + Hashes LocalHashesCalc; + LocalHashesCalc.AddFD(fd); + HashStringList const LocalHashes = LocalHashesCalc.GetHashStringList(); + + if(Debug) + std::clog << "QueueNextDiff: " << PartialFile << " (" << LocalHashes.find(NULL)->toStr() << ")" << std::endl; + + HashStringList const TargetFileHashes = GetExpectedHashesFor(Target.MetaKey); + if (unlikely(LocalHashes.usable() == false || TargetFileHashes.usable() == false)) + { + Failed("Local/Expected hashes are not usable for " + PartialFile, NULL); + return false; + } + + // final file reached before all patches are applied + if(LocalHashes == TargetFileHashes) + { + Finish(true); + return true; + } + + // remove all patches until the next matching patch is found + // this requires the Index file to be ordered + available_patches.erase(available_patches.begin(), + std::find_if(available_patches.begin(), available_patches.end(), [&](DiffInfo const &I) { + return I.result_hashes == LocalHashes; + })); + + // error checking and falling back if no patch was found + if(available_patches.empty() == true) + { + Failed("No patches left to reach target for " + PartialFile, NULL); + return false; + } + + // queue the right diff + Desc.URI = Target.URI + ".diff/" + available_patches[0].file + ".gz"; + Desc.Description = Target.Description + " " + available_patches[0].file + string(".pdiff"); + DestFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI + ".diff/" + available_patches[0].file), Target); + + if(Debug) + std::clog << "pkgAcqIndexDiffs::QueueNextDiff(): " << Desc.URI << std::endl; + + QueueURI(Desc); + + return true; +} + /*}}}*/ +void pkgAcqIndexDiffs::Done(string const &Message, HashStringList const &Hashes, /*{{{*/ + pkgAcquire::MethodConfig const * const Cnf) +{ + if (Debug) + std::clog << "pkgAcqIndexDiffs::Done(): " << Desc.URI << std::endl; + + Item::Done(Message, Hashes, Cnf); + + std::string const UncompressedUnpatchedFile = GetPartialFileNameFromURI(Target.URI); + std::string const UnpatchedFile = GetExistingFilename(UncompressedUnpatchedFile); + std::string const PatchFile = GetDiffsPatchFileName(UnpatchedFile); + std::string const PatchedFile = GetKeepCompressedFileName(UncompressedUnpatchedFile, Target); + + switch (State) + { + // success in downloading a diff, enter ApplyDiff state + case StateFetchDiff: + Rename(DestFile, PatchFile); + DestFile = GetKeepCompressedFileName(UncompressedUnpatchedFile + "-patched", Target); + if(Debug) + std::clog << "Sending to rred method: " << UnpatchedFile << std::endl; + State = StateApplyDiff; + Local = true; + Desc.URI = "rred:" + UnpatchedFile; + QueueURI(Desc); + SetActiveSubprocess("rred"); + return; + // success in download/apply a diff, queue next (if needed) + case StateApplyDiff: + // remove the just applied patch and base file + available_patches.erase(available_patches.begin()); + RemoveFile("pkgAcqIndexDiffs::Done", PatchFile); + RemoveFile("pkgAcqIndexDiffs::Done", UnpatchedFile); + if(Debug) + std::clog << "Moving patched file in place: " << std::endl + << DestFile << " -> " << PatchedFile << std::endl; + Rename(DestFile, PatchedFile); + + // see if there is more to download + if(available_patches.empty() == false) + { + new pkgAcqIndexDiffs(Owner, TransactionManager, Target, available_patches); + Finish(); + } else { + DestFile = PatchedFile; + Finish(true); + } + return; + } +} + /*}}}*/ +std::string pkgAcqIndexDiffs::Custom600Headers() const /*{{{*/ +{ + if(State != StateApplyDiff) + return pkgAcqBaseIndex::Custom600Headers(); + std::ostringstream patchhashes; + for (auto && hs : available_patches[0].result_hashes) + patchhashes << "\nStart-" << hs.HashType() << "-Hash: " << hs.HashValue(); + for (auto && hs : available_patches[0].patch_hashes) + patchhashes << "\nPatch-0-" << hs.HashType() << "-Hash: " << hs.HashValue(); + patchhashes << pkgAcqBaseIndex::Custom600Headers(); + return patchhashes.str(); +} + /*}}}*/ +pkgAcqIndexDiffs::~pkgAcqIndexDiffs() {} + +// AcqIndexMergeDiffs::AcqIndexMergeDiffs - Constructor /*{{{*/ +pkgAcqIndexMergeDiffs::pkgAcqIndexMergeDiffs(pkgAcquire *const Owner, + pkgAcqMetaClearSig *const TransactionManager, + IndexTarget const &Target, + DiffInfo const &patch, + std::vector<pkgAcqIndexMergeDiffs *> const *const allPatches) + : pkgAcqBaseIndex(Owner, TransactionManager, Target), + patch(patch), allPatches(allPatches), State(StateFetchDiff) +{ + Debug = _config->FindB("Debug::pkgAcquire::Diffs",false); + + Desc.Owner = this; + Desc.ShortDesc = Target.ShortDesc; + Desc.URI = Target.URI + ".diff/" + patch.file + ".gz"; + Desc.Description = Target.Description + " " + patch.file + ".pdiff"; + DestFile = GetPartialFileNameFromURI(Target.URI + ".diff/" + patch.file + ".gz"); + + if(Debug) + std::clog << "pkgAcqIndexMergeDiffs: " << Desc.URI << std::endl; + + QueueURI(Desc); +} + /*}}}*/ +void pkgAcqIndexMergeDiffs::Failed(string const &Message,pkgAcquire::MethodConfig const * const Cnf)/*{{{*/ +{ + if(Debug) + std::clog << "pkgAcqIndexMergeDiffs failed: " << Desc.URI << " with " << Message << std::endl; + + pkgAcqBaseIndex::Failed(Message,Cnf); + Status = StatDone; + + // check if we are the first to fail, otherwise we are done here + State = StateDoneDiff; + for (std::vector<pkgAcqIndexMergeDiffs *>::const_iterator I = allPatches->begin(); + I != allPatches->end(); ++I) + if ((*I)->State == StateErrorDiff) + { + State = StateErrorDiff; + return; + } + + // first failure means we should fallback + State = StateErrorDiff; + if (Debug) + std::clog << "Falling back to normal index file acquire" << std::endl; + RenameOnError(PDiffError); + std::string const UnpatchedFile = GetExistingFilename(GetPartialFileNameFromURI(Target.URI)); + if (UnpatchedFile.empty() == false && FileExists(UnpatchedFile)) + Rename(UnpatchedFile, UnpatchedFile + ".FAILED"); + DestFile.clear(); + new pkgAcqIndex(Owner, TransactionManager, Target); +} + /*}}}*/ +void pkgAcqIndexMergeDiffs::Done(string const &Message, HashStringList const &Hashes, /*{{{*/ + pkgAcquire::MethodConfig const * const Cnf) +{ + if(Debug) + std::clog << "pkgAcqIndexMergeDiffs::Done(): " << Desc.URI << std::endl; + + Item::Done(Message, Hashes, Cnf); + + if (std::any_of(allPatches->begin(), allPatches->end(), + [](pkgAcqIndexMergeDiffs const * const P) { return P->State == StateErrorDiff; })) + { + if(Debug) + std::clog << "Another patch failed already, no point in processing this one." << std::endl; + State = StateErrorDiff; + return; + } + + std::string const UncompressedUnpatchedFile = GetPartialFileNameFromURI(Target.URI); + std::string const UnpatchedFile = GetExistingFilename(UncompressedUnpatchedFile); + if (UnpatchedFile.empty()) + { + _error->Fatal("Unpatched file %s doesn't exist (anymore)!", UncompressedUnpatchedFile.c_str()); + State = StateErrorDiff; + return; + } + std::string const PatchFile = GetMergeDiffsPatchFileName(UnpatchedFile, patch.file); + std::string const PatchedFile = GetKeepCompressedFileName(UncompressedUnpatchedFile, Target); + + switch (State) + { + case StateFetchDiff: + Rename(DestFile, PatchFile); + + // check if this is the last completed diff + State = StateDoneDiff; + for (std::vector<pkgAcqIndexMergeDiffs *>::const_iterator I = allPatches->begin(); + I != allPatches->end(); ++I) + if ((*I)->State != StateDoneDiff) + { + if(Debug) + std::clog << "Not the last done diff in the batch: " << Desc.URI << std::endl; + return; + } + // this is the last completed diff, so we are ready to apply now + DestFile = GetKeepCompressedFileName(UncompressedUnpatchedFile + "-patched", Target); + if(Debug) + std::clog << "Sending to rred method: " << UnpatchedFile << std::endl; + State = StateApplyDiff; + Local = true; + Desc.URI = "rred:" + UnpatchedFile; + QueueURI(Desc); + SetActiveSubprocess("rred"); + return; + case StateApplyDiff: + // success in download & apply all diffs, finialize and clean up + if(Debug) + std::clog << "Queue patched file in place: " << std::endl + << DestFile << " -> " << PatchedFile << std::endl; + + // queue for copy by the transaction manager + TransactionManager->TransactionStageCopy(this, DestFile, GetKeepCompressedFileName(GetFinalFilename(), Target)); + + // ensure the ed's are gone regardless of list-cleanup + for (std::vector<pkgAcqIndexMergeDiffs *>::const_iterator I = allPatches->begin(); + I != allPatches->end(); ++I) + RemoveFile("pkgAcqIndexMergeDiffs::Done", GetMergeDiffsPatchFileName(UnpatchedFile, (*I)->patch.file)); + RemoveFile("pkgAcqIndexMergeDiffs::Done", UnpatchedFile); + + // all set and done + Complete = true; + if(Debug) + std::clog << "allDone: " << DestFile << "\n" << std::endl; + return; + case StateDoneDiff: _error->Fatal("Done called for %s which is in an invalid Done state", PatchFile.c_str()); break; + case StateErrorDiff: _error->Fatal("Done called for %s which is in an invalid Error state", PatchFile.c_str()); break; + } +} + /*}}}*/ +std::string pkgAcqIndexMergeDiffs::Custom600Headers() const /*{{{*/ +{ + if(State != StateApplyDiff) + return pkgAcqBaseIndex::Custom600Headers(); + std::ostringstream patchhashes; + unsigned int seen_patches = 0; + for (auto && hs : (*allPatches)[0]->patch.result_hashes) + patchhashes << "\nStart-" << hs.HashType() << "-Hash: " << hs.HashValue(); + for (std::vector<pkgAcqIndexMergeDiffs *>::const_iterator I = allPatches->begin(); + I != allPatches->end(); ++I) + { + HashStringList const ExpectedHashes = (*I)->patch.patch_hashes; + for (HashStringList::const_iterator hs = ExpectedHashes.begin(); hs != ExpectedHashes.end(); ++hs) + patchhashes << "\nPatch-" << std::to_string(seen_patches) << "-" << hs->HashType() << "-Hash: " << hs->HashValue(); + ++seen_patches; + } + patchhashes << pkgAcqBaseIndex::Custom600Headers(); + return patchhashes.str(); +} + /*}}}*/ +pkgAcqIndexMergeDiffs::~pkgAcqIndexMergeDiffs() {} + +// AcqIndex::AcqIndex - Constructor /*{{{*/ +pkgAcqIndex::pkgAcqIndex(pkgAcquire * const Owner, + pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &Target, bool const Derived) + : pkgAcqBaseIndex(Owner, TransactionManager, Target), d(NULL), Stage(STAGE_DOWNLOAD), + CompressionExtensions(Target.Option(IndexTarget::COMPRESSIONTYPES)) +{ + if (Derived) + return; + Init(Target.URI, Target.Description, Target.ShortDesc); + + if(_config->FindB("Debug::Acquire::Transaction", false) == true) + std::clog << "New pkgIndex with TransactionManager " + << TransactionManager << std::endl; +} + /*}}}*/ +// AcqIndex::Init - deferred Constructor /*{{{*/ +void pkgAcqIndex::Init(string const &URI, string const &URIDesc, + string const &ShortDesc) +{ + Stage = STAGE_DOWNLOAD; + + DestFile = GetPartialFileNameFromURI(URI); + size_t const nextExt = CompressionExtensions.find(' '); + if (nextExt == std::string::npos) + { + CurrentCompressionExtension = CompressionExtensions; + CompressionExtensions.clear(); + } + else + { + CurrentCompressionExtension = CompressionExtensions.substr(0, nextExt); + CompressionExtensions = CompressionExtensions.substr(nextExt+1); + } + + if (CurrentCompressionExtension == "uncompressed") + { + Desc.URI = URI; + } + else if (unlikely(CurrentCompressionExtension.empty())) + return; + else + { + Desc.URI = URI + '.' + CurrentCompressionExtension; + DestFile = DestFile + '.' + CurrentCompressionExtension; + } + + // store file size of the download to ensure the fetcher gives + // accurate progress reporting + FileSize = GetExpectedHashes().FileSize(); + + Desc.Description = URIDesc; + Desc.Owner = this; + Desc.ShortDesc = ShortDesc; + + QueueURI(Desc); +} + /*}}}*/ +// AcqIndex::Custom600Headers - Insert custom request headers /*{{{*/ +// --------------------------------------------------------------------- +/* The only header we use is the last-modified header. */ +string pkgAcqIndex::Custom600Headers() const +{ + + string msg = "\nIndex-File: true"; + + if (TransactionManager->LastMetaIndexParser == NULL) + { + std::string const Final = GetFinalFilename(); + + struct stat Buf; + if (stat(Final.c_str(),&Buf) == 0) + msg += "\nLast-Modified: " + TimeRFC1123(Buf.st_mtime, false); + } + + if(Target.IsOptional) + msg += "\nFail-Ignore: true"; + + return msg; +} + /*}}}*/ +// AcqIndex::Failed - getting the indexfile failed /*{{{*/ +bool pkgAcqIndex::CommonFailed(std::string const &TargetURI, + std::string const &Message, pkgAcquire::MethodConfig const *const Cnf) +{ + pkgAcqBaseIndex::Failed(Message,Cnf); + // authorisation matches will not be fixed by other compression types + if (Status != StatAuthError) + { + if (CompressionExtensions.empty() == false) + { + Init(TargetURI, Desc.Description, Desc.ShortDesc); + Status = StatIdle; + return true; + } + } + return false; +} +void pkgAcqIndex::Failed(string const &Message,pkgAcquire::MethodConfig const * const Cnf) +{ + if (CommonFailed(Target.URI, Message, Cnf)) + return; + + if(Target.IsOptional && GetExpectedHashes().empty() && Stage == STAGE_DOWNLOAD) + Status = StatDone; + else + TransactionManager->AbortTransaction(); +} + /*}}}*/ +// AcqIndex::Done - Finished a fetch /*{{{*/ +// --------------------------------------------------------------------- +/* This goes through a number of states.. On the initial fetch the + method could possibly return an alternate filename which points + to the uncompressed version of the file. If this is so the file + is copied into the partial directory. In all other cases the file + is decompressed with a compressed uri. */ +void pkgAcqIndex::Done(string const &Message, + HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cfg) +{ + Item::Done(Message,Hashes,Cfg); + + switch(Stage) + { + case STAGE_DOWNLOAD: + StageDownloadDone(Message); + break; + case STAGE_DECOMPRESS_AND_VERIFY: + StageDecompressDone(); + break; + } +} + /*}}}*/ +// AcqIndex::StageDownloadDone - Queue for decompress and verify /*{{{*/ +void pkgAcqIndex::StageDownloadDone(string const &Message) +{ + Local = true; + Complete = true; + + std::string const AltFilename = LookupTag(Message,"Alt-Filename"); + std::string Filename = LookupTag(Message,"Filename"); + + // we need to verify the file against the current Release file again + // on if-modfied-since hit to avoid a stale attack against us + if(StringToBool(LookupTag(Message,"IMS-Hit"),false) == true) + { + // copy FinalFile into partial/ so that we check the hash again + string const FinalFile = GetExistingFilename(GetFinalFileNameFromURI(Target.URI)); + if (symlink(FinalFile.c_str(), DestFile.c_str()) != 0) + _error->WarningE("pkgAcqIndex::StageDownloadDone", "Symlinking final file %s back to %s failed", FinalFile.c_str(), DestFile.c_str()); + else + { + EraseFileName = DestFile; + Filename = DestFile; + } + Stage = STAGE_DECOMPRESS_AND_VERIFY; + Desc.URI = "store:" + Filename; + QueueURI(Desc); + SetActiveSubprocess(::URI(Desc.URI).Access); + return; + } + // methods like file:// give us an alternative (uncompressed) file + else if (Target.KeepCompressed == false && AltFilename.empty() == false) + { + Filename = AltFilename; + EraseFileName.clear(); + } + // Methods like e.g. "file:" will give us a (compressed) FileName that is + // not the "DestFile" we set, in this case we uncompress from the local file + else if (Filename != DestFile && RealFileExists(DestFile) == false) + { + // symlinking ensures that the filename can be used for compression detection + // that is e.g. needed for by-hash which has no extension over file + if (symlink(Filename.c_str(),DestFile.c_str()) != 0) + _error->WarningE("pkgAcqIndex::StageDownloadDone", "Symlinking file %s to %s failed", Filename.c_str(), DestFile.c_str()); + else + { + EraseFileName = DestFile; + Filename = DestFile; + } + } + + Stage = STAGE_DECOMPRESS_AND_VERIFY; + DestFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI), Target); + if (Filename != DestFile && flExtension(Filename) == flExtension(DestFile)) + Desc.URI = "copy:" + Filename; + else + Desc.URI = "store:" + Filename; + if (DestFile == Filename) + { + if (CurrentCompressionExtension == "uncompressed") + return StageDecompressDone(); + DestFile = "/dev/null"; + } + + if (EraseFileName.empty() && Filename != AltFilename) + EraseFileName = Filename; + + // queue uri for the next stage + QueueURI(Desc); + SetActiveSubprocess(::URI(Desc.URI).Access); +} + /*}}}*/ +// AcqIndex::StageDecompressDone - Final verification /*{{{*/ +void pkgAcqIndex::StageDecompressDone() +{ + if (DestFile == "/dev/null") + DestFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI), Target); + + // Done, queue for rename on transaction finished + TransactionManager->TransactionStageCopy(this, DestFile, GetFinalFilename()); +} + /*}}}*/ +pkgAcqIndex::~pkgAcqIndex() {} + +// AcqArchive::AcqArchive - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* This just sets up the initial fetch environment and queues the first + possibilitiy */ +APT_IGNORE_DEPRECATED_PUSH +pkgAcqArchive::pkgAcqArchive(pkgAcquire *const Owner, pkgSourceList *const Sources, + pkgRecords *const Recs, pkgCache::VerIterator const &Version, + string &StoreFilename) : Item(Owner), d(NULL), LocalSource(false), Version(Version), Sources(Sources), Recs(Recs), + StoreFilename(StoreFilename), Vf(), + Trusted(false) +{ + if (Version.Arch() == 0) + { + _error->Error(_("I wasn't able to locate a file for the %s package. " + "This might mean you need to manually fix this package. " + "(due to missing arch)"), + Version.ParentPkg().FullName().c_str()); + return; + } + + // check if we have one trusted source for the package. if so, switch + // to "TrustedOnly" mode - but only if not in AllowUnauthenticated mode + bool const allowUnauth = _config->FindB("APT::Get::AllowUnauthenticated", false); + bool const debugAuth = _config->FindB("Debug::pkgAcquire::Auth", false); + bool seenUntrusted = false; + for (pkgCache::VerFileIterator i = Version.FileList(); i.end() == false; ++i) + { + pkgIndexFile *Index; + if (Sources->FindIndex(i.File(),Index) == false) + continue; + + if (debugAuth == true) + std::cerr << "Checking index: " << Index->Describe() + << "(Trusted=" << Index->IsTrusted() << ")" << std::endl; + + if (Index->IsTrusted() == true) + { + Trusted = true; + if (allowUnauth == false) + break; + } + else + seenUntrusted = true; + } + + // "allow-unauthenticated" restores apts old fetching behaviour + // that means that e.g. unauthenticated file:// uris are higher + // priority than authenticated http:// uris + if (allowUnauth == true && seenUntrusted == true) + Trusted = false; + + StoreFilename.clear(); + std::set<string> targetComponents, targetCodenames, targetSuites; + std::vector<std::unique_ptr<FileFd>> authconfs; + for (auto Vf = Version.FileList(); Vf.end() == false; ++Vf) + { + auto const PkgF = Vf.File(); + if (unlikely(PkgF.end())) + continue; + if (PkgF.Flagged(pkgCache::Flag::NotSource)) + continue; + if (PkgF.Flagged(pkgCache::Flag::PackagesRequireAuthorization) && !IsAuthorized(PkgF, authconfs)) + continue; + pkgIndexFile *Index; + if (Sources->FindIndex(PkgF, Index) == false) + continue; + if (Trusted && Index->IsTrusted() == false) + continue; + + pkgRecords::Parser &Parse = Recs->Lookup(Vf); + // collect the hashes from the indexes + auto hsl = Parse.Hashes(); + if (ExpectedHashes.empty()) + ExpectedHashes = hsl; + else + { + // bad things will likely happen, but the user might be "lucky" still + // if the sources provide the same hashtypes (so that they aren't mixed) + for (auto const &hs : hsl) + if (ExpectedHashes.push_back(hs) == false) + { + _error->Warning("Sources disagree on hashes for supposely identical version '%s' of '%s'.", + Version.VerStr(), Version.ParentPkg().FullName(false).c_str()); + break; + } + } + // only allow local volatile sources to have no hashes + if (PkgF.Flagged(pkgCache::Flag::LocalSource)) + LocalSource = true; + else if (hsl.empty()) + continue; + + std::string poolfilename = Parse.FileName(); + if (poolfilename.empty()) + continue; + + std::remove_reference<decltype(ModifyCustomFields())>::type fields; + { + auto const debIndex = dynamic_cast<pkgDebianIndexTargetFile const *const>(Index); + if (debIndex != nullptr) + { + auto const IT = debIndex->GetIndexTarget(); + fields.emplace("Target-Repo-URI", IT.Option(IndexTarget::REPO_URI)); + fields.emplace("Target-Release", IT.Option(IndexTarget::RELEASE)); + fields.emplace("Target-Site", IT.Option(IndexTarget::SITE)); + } + } + fields.emplace("Target-Base-URI", Index->ArchiveURI("")); + if (PkgF->Component != 0) + fields.emplace("Target-Component", PkgF.Component()); + auto const RelF = PkgF.ReleaseFile(); + if (RelF.end() == false) + { + if (RelF->Codename != 0) + fields.emplace("Target-Codename", RelF.Codename()); + if (RelF->Archive != 0) + fields.emplace("Target-Suite", RelF.Archive()); + } + fields.emplace("Target-Architecture", Version.Arch()); + fields.emplace("Target-Type", flExtension(poolfilename)); + + if (StoreFilename.empty()) + { + /* We pick a filename based on the information we have for the version, + but we don't know what extension such a file should have, so we look + at the filenames used online and assume that they are the same for + all repositories containing this file */ + StoreFilename = QuoteString(Version.ParentPkg().Name(), "_:") + '_' + + QuoteString(Version.VerStr(), "_:") + '_' + + QuoteString(Version.Arch(), "_:.") + + "." + flExtension(poolfilename); + + Desc.URI = Index->ArchiveURI(poolfilename); + Desc.Description = Index->ArchiveInfo(Version); + Desc.Owner = this; + Desc.ShortDesc = Version.ParentPkg().FullName(true); + auto &customfields = ModifyCustomFields(); + for (auto const &f : fields) + customfields[f.first] = f.second; + FileSize = Version->Size; + } + else + PushAlternativeURI(Index->ArchiveURI(poolfilename), std::move(fields), true); + } + if (StoreFilename.empty()) + { + _error->Error(_("Can't find a source to download version '%s' of '%s'"), + Version.VerStr(), Version.ParentPkg().FullName(false).c_str()); + return; + } + + // Check if we already downloaded the file + struct stat Buf; + auto FinalFile = _config->FindDir("Dir::Cache::Archives") + flNotDir(StoreFilename); + if (stat(FinalFile.c_str(), &Buf) == 0) + { + // Make sure the size matches + if ((unsigned long long)Buf.st_size == Version->Size) + { + Complete = true; + Local = true; + Status = StatDone; + StoreFilename = DestFile = FinalFile; + return; + } + + /* Hmm, we have a file and its size does not match, this shouldn't + happen.. */ + RemoveFile("pkgAcqArchive::QueueNext", FinalFile); + } + + // Check the destination file + DestFile = _config->FindDir("Dir::Cache::Archives") + "partial/" + flNotDir(StoreFilename); + if (stat(DestFile.c_str(), &Buf) == 0) + { + // Hmm, the partial file is too big, erase it + if ((unsigned long long)Buf.st_size > Version->Size) + RemoveFile("pkgAcqArchive::QueueNext", DestFile); + else + PartialSize = Buf.st_size; + } + + // Disables download of archives - useful if no real installation follows, + // e.g. if we are just interested in proposed installation order + if (_config->FindB("Debug::pkgAcqArchive::NoQueue", false) == true) + { + Complete = true; + Local = true; + Status = StatDone; + StoreFilename = DestFile = FinalFile; + return; + } + + // Create the item + Local = false; + QueueURI(Desc); +} +APT_IGNORE_DEPRECATED_POP + /*}}}*/ +bool pkgAcqArchive::QueueNext() /*{{{*/ +{ + return false; +} + /*}}}*/ +// AcqArchive::Done - Finished fetching /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgAcqArchive::Done(string const &Message, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cfg) +{ + Item::Done(Message, Hashes, Cfg); + + // Grab the output filename + std::string const FileName = LookupTag(Message,"Filename"); + if (DestFile != FileName && RealFileExists(DestFile) == false) + { + StoreFilename = DestFile = FileName; + Local = true; + Complete = true; + return; + } + + // Done, move it into position + string const FinalFile = GetFinalFilename(); + Rename(DestFile,FinalFile); + StoreFilename = DestFile = FinalFile; + Complete = true; +} + /*}}}*/ +// AcqArchive::Failed - Failure handler /*{{{*/ +// --------------------------------------------------------------------- +/* Here we try other sources */ +void pkgAcqArchive::Failed(string const &Message,pkgAcquire::MethodConfig const * const Cnf) +{ + Item::Failed(Message,Cnf); +} + /*}}}*/ +APT_PURE bool pkgAcqArchive::IsTrusted() const /*{{{*/ +{ + return Trusted; +} + /*}}}*/ +void pkgAcqArchive::Finished() /*{{{*/ +{ + if (Status == pkgAcquire::Item::StatDone && + Complete == true) + return; + StoreFilename = string(); +} + /*}}}*/ +std::string pkgAcqArchive::DescURI() const /*{{{*/ +{ + return Desc.URI; +} + /*}}}*/ +std::string pkgAcqArchive::ShortDesc() const /*{{{*/ +{ + return Desc.ShortDesc; +} + /*}}}*/ +pkgAcqArchive::~pkgAcqArchive() {} + +// AcqChangelog::pkgAcqChangelog - Constructors /*{{{*/ +class pkgAcqChangelog::Private +{ + public: + std::string FinalFile; +}; +pkgAcqChangelog::pkgAcqChangelog(pkgAcquire * const Owner, pkgCache::VerIterator const &Ver, + std::string const &DestDir, std::string const &DestFilename) : + pkgAcquire::Item(Owner), d(new pkgAcqChangelog::Private()), SrcName(Ver.SourcePkgName()), SrcVersion(Ver.SourceVerStr()) +{ + Desc.URI = URI(Ver); + Init(DestDir, DestFilename); +} +// some parameters are char* here as they come likely from char* interfaces – which can also return NULL +pkgAcqChangelog::pkgAcqChangelog(pkgAcquire * const Owner, pkgCache::RlsFileIterator const &RlsFile, + char const * const Component, char const * const SrcName, char const * const SrcVersion, + const string &DestDir, const string &DestFilename) : + pkgAcquire::Item(Owner), d(new pkgAcqChangelog::Private()), SrcName(SrcName), SrcVersion(SrcVersion) +{ + Desc.URI = URI(RlsFile, Component, SrcName, SrcVersion); + Init(DestDir, DestFilename); +} +pkgAcqChangelog::pkgAcqChangelog(pkgAcquire * const Owner, + std::string const &URI, char const * const SrcName, char const * const SrcVersion, + const string &DestDir, const string &DestFilename) : + pkgAcquire::Item(Owner), d(new pkgAcqChangelog::Private()), SrcName(SrcName), SrcVersion(SrcVersion) +{ + Desc.URI = URI; + Init(DestDir, DestFilename); +} +void pkgAcqChangelog::Init(std::string const &DestDir, std::string const &DestFilename) +{ + if (Desc.URI.empty()) + { + Status = StatError; + // TRANSLATOR: %s=%s is sourcename=sourceversion, e.g. apt=1.1 + strprintf(ErrorText, _("Changelog unavailable for %s=%s"), SrcName.c_str(), SrcVersion.c_str()); + // Let the error message print something sensible rather than "Failed to fetch /" + if (DestFilename.empty()) + DestFile = SrcName + ".changelog"; + else + DestFile = DestFilename; + Desc.URI = "changelog:/" + DestFile; + return; + } + + std::string DestFileName; + if (DestFilename.empty()) + DestFileName = flCombine(DestFile, SrcName + ".changelog"); + else + DestFileName = flCombine(DestFile, DestFilename); + + std::string const SandboxUser = _config->Find("APT::Sandbox::User"); + std::string const systemTemp = GetTempDir(SandboxUser); + char tmpname[1000]; + snprintf(tmpname, sizeof(tmpname), "%s/apt-changelog-XXXXXX", systemTemp.c_str()); + if (NULL == mkdtemp(tmpname)) + { + _error->Errno("mkdtemp", "mkdtemp failed in changelog acquire of %s %s", SrcName.c_str(), SrcVersion.c_str()); + Status = StatError; + return; + } + TemporaryDirectory = tmpname; + + ChangeOwnerAndPermissionOfFile("pkgAcqChangelog::Init", TemporaryDirectory.c_str(), + SandboxUser.c_str(), ROOT_GROUP, 0700); + + DestFile = flCombine(TemporaryDirectory, DestFileName); + if (DestDir.empty() == false) + { + d->FinalFile = flCombine(DestDir, DestFileName); + if (RealFileExists(d->FinalFile)) + { + FileFd file1, file2; + if (file1.Open(DestFile, FileFd::WriteOnly | FileFd::Create | FileFd::Exclusive) && + file2.Open(d->FinalFile, FileFd::ReadOnly) && CopyFile(file2, file1)) + { + ChangeOwnerAndPermissionOfFile("pkgAcqChangelog::Init", DestFile.c_str(), "root", ROOT_GROUP, 0644); + struct timeval times[2]; + times[0].tv_sec = times[1].tv_sec = file2.ModificationTime(); + times[0].tv_usec = times[1].tv_usec = 0; + utimes(DestFile.c_str(), times); + } + } + } + + Desc.ShortDesc = "Changelog"; + strprintf(Desc.Description, "%s %s %s Changelog", URI::SiteOnly(Desc.URI).c_str(), SrcName.c_str(), SrcVersion.c_str()); + Desc.Owner = this; + QueueURI(Desc); +} + /*}}}*/ +std::string pkgAcqChangelog::URI(pkgCache::VerIterator const &Ver) /*{{{*/ +{ + std::string const confOnline = "Acquire::Changelogs::AlwaysOnline"; + bool AlwaysOnline = _config->FindB(confOnline, false); + if (AlwaysOnline == false) + for (pkgCache::VerFileIterator VF = Ver.FileList(); VF.end() == false; ++VF) + { + pkgCache::PkgFileIterator const PF = VF.File(); + if (PF.Flagged(pkgCache::Flag::NotSource) || PF->Release == 0) + continue; + pkgCache::RlsFileIterator const RF = PF.ReleaseFile(); + if (RF->Origin != 0 && _config->FindB(confOnline + "::Origin::" + RF.Origin(), false)) + { + AlwaysOnline = true; + break; + } + } + if (AlwaysOnline == false) + { + pkgCache::PkgIterator const Pkg = Ver.ParentPkg(); + if (Pkg->CurrentVer != 0 && Pkg.CurrentVer() == Ver) + { + std::string const root = _config->FindDir("Dir"); + std::string const basename = root + std::string("usr/share/doc/") + Pkg.Name() + "/changelog"; + std::string const debianname = basename + ".Debian"; + if (FileExists(debianname)) + return "copy://" + debianname; + else if (FileExists(debianname + ".gz")) + return "store://" + debianname + ".gz"; + else if (FileExists(basename)) + return "copy://" + basename; + else if (FileExists(basename + ".gz")) + return "store://" + basename + ".gz"; + } + } + + char const * const SrcName = Ver.SourcePkgName(); + char const * const SrcVersion = Ver.SourceVerStr(); + // find the first source for this version which promises a changelog + for (pkgCache::VerFileIterator VF = Ver.FileList(); VF.end() == false; ++VF) + { + pkgCache::PkgFileIterator const PF = VF.File(); + if (PF.Flagged(pkgCache::Flag::NotSource) || PF->Release == 0) + continue; + pkgCache::RlsFileIterator const RF = PF.ReleaseFile(); + std::string const uri = URI(RF, PF.Component(), SrcName, SrcVersion); + if (uri.empty()) + continue; + return uri; + } + return ""; +} +std::string pkgAcqChangelog::URITemplate(pkgCache::RlsFileIterator const &Rls) +{ + if (Rls.end() == true || (Rls->Label == 0 && Rls->Origin == 0)) + return ""; + std::string const serverConfig = "Acquire::Changelogs::URI"; + std::string server; +#define APT_EMPTY_SERVER \ + if (server.empty() == false) \ + { \ + if (server != "no") \ + return server; \ + return ""; \ + } +#define APT_CHECK_SERVER(X, Y) \ + if (Rls->X != 0) \ + { \ + std::string const specialServerConfig = serverConfig + "::" + Y + #X + "::" + Rls.X(); \ + server = _config->Find(specialServerConfig); \ + APT_EMPTY_SERVER \ + } + // this way e.g. Debian-Security can fallback to Debian + APT_CHECK_SERVER(Label, "Override::") + APT_CHECK_SERVER(Origin, "Override::") + + if (RealFileExists(Rls.FileName())) + { + _error->PushToStack(); + FileFd rf; + /* This can be costly. A caller wanting to get millions of URIs might + want to do this on its own once and use Override settings. + We don't do this here as Origin/Label are not as unique as they + should be so this could produce request order-dependent anomalies */ + if (OpenMaybeClearSignedFile(Rls.FileName(), rf) == true) + { + pkgTagFile TagFile(&rf, rf.Size()); + pkgTagSection Section; + if (TagFile.Step(Section) == true) + server = Section.FindS("Changelogs"); + } + _error->RevertToStack(); + APT_EMPTY_SERVER + } + + APT_CHECK_SERVER(Label, "") + APT_CHECK_SERVER(Origin, "") +#undef APT_CHECK_SERVER +#undef APT_EMPTY_SERVER + return ""; +} +std::string pkgAcqChangelog::URI(pkgCache::RlsFileIterator const &Rls, + char const * const Component, char const * const SrcName, + char const * const SrcVersion) +{ + return URI(URITemplate(Rls), Component, SrcName, SrcVersion); +} +std::string pkgAcqChangelog::URI(std::string const &Template, + char const * const Component, char const * const SrcName, + char const * const SrcVersion) +{ + if (Template.find("@CHANGEPATH@") == std::string::npos) + return ""; + + // the path is: COMPONENT/SRC/SRCNAME/SRCNAME_SRCVER, e.g. main/a/apt/apt_1.1 or contrib/liba/libapt/libapt_2.0 + std::string Src = SrcName; + std::string path = APT::String::Startswith(SrcName, "lib") ? Src.substr(0, 4) : Src.substr(0,1); + path.append("/").append(Src).append("/"); + path.append(Src).append("_").append(StripEpoch(SrcVersion)); + // we omit component for releases without one (= flat-style repositories) + if (Component != NULL && strlen(Component) != 0) + path = std::string(Component) + "/" + path; + + return SubstVar(Template, "@CHANGEPATH@", path); +} + /*}}}*/ +// AcqChangelog::Failed - Failure handler /*{{{*/ +void pkgAcqChangelog::Failed(string const &Message, pkgAcquire::MethodConfig const * const Cnf) +{ + Item::Failed(Message,Cnf); + + std::string errText; + // TRANSLATOR: %s=%s is sourcename=sourceversion, e.g. apt=1.1 + strprintf(errText, _("Changelog unavailable for %s=%s"), SrcName.c_str(), SrcVersion.c_str()); + + // Error is probably something techy like 404 Not Found + if (ErrorText.empty()) + ErrorText = errText; + else + ErrorText = errText + " (" + ErrorText + ")"; +} + /*}}}*/ +// AcqChangelog::Done - Item downloaded OK /*{{{*/ +void pkgAcqChangelog::Done(string const &Message,HashStringList const &CalcHashes, + pkgAcquire::MethodConfig const * const Cnf) +{ + Item::Done(Message,CalcHashes,Cnf); + if (d->FinalFile.empty() == false) + { + if (RemoveFile("pkgAcqChangelog::Done", d->FinalFile) == false || + Rename(DestFile, d->FinalFile) == false) + Status = StatError; + } + + Complete = true; +} + /*}}}*/ +pkgAcqChangelog::~pkgAcqChangelog() /*{{{*/ +{ + if (TemporaryDirectory.empty() == false) + { + RemoveFile("~pkgAcqChangelog", DestFile); + rmdir(TemporaryDirectory.c_str()); + } + delete d; +} + /*}}}*/ + +// AcqFile::pkgAcqFile - Constructor /*{{{*/ +APT_IGNORE_DEPRECATED_PUSH +pkgAcqFile::pkgAcqFile(pkgAcquire *const Owner, string const &URI, HashStringList const &Hashes, + unsigned long long const Size, string const &Dsc, string const &ShortDesc, + const string &DestDir, const string &DestFilename, + bool const IsIndexFile) : Item(Owner), d(NULL), Retries(0), IsIndexFile(IsIndexFile), ExpectedHashes(Hashes) +{ + if(!DestFilename.empty()) + DestFile = DestFilename; + else if(!DestDir.empty()) + DestFile = DestDir + "/" + flNotDir(URI); + else + DestFile = flNotDir(URI); + + // Create the item + Desc.URI = URI; + Desc.Description = Dsc; + Desc.Owner = this; + + // Set the short description to the archive component + Desc.ShortDesc = ShortDesc; + + // Get the transfer sizes + FileSize = Size; + struct stat Buf; + if (stat(DestFile.c_str(),&Buf) == 0) + { + // Hmm, the partial file is too big, erase it + if ((Size > 0) && (unsigned long long)Buf.st_size > Size) + RemoveFile("pkgAcqFile", DestFile); + else + PartialSize = Buf.st_size; + } + + QueueURI(Desc); +} +APT_IGNORE_DEPRECATED_POP + /*}}}*/ +// AcqFile::Done - Item downloaded OK /*{{{*/ +void pkgAcqFile::Done(string const &Message,HashStringList const &CalcHashes, + pkgAcquire::MethodConfig const * const Cnf) +{ + Item::Done(Message,CalcHashes,Cnf); + + std::string const FileName = LookupTag(Message,"Filename"); + Complete = true; + + // The files timestamp matches + if (StringToBool(LookupTag(Message,"IMS-Hit"),false) == true) + return; + + // We have to copy it into place + if (RealFileExists(DestFile.c_str()) == false) + { + Local = true; + if (_config->FindB("Acquire::Source-Symlinks",true) == false || + Cnf->Removable == true) + { + Desc.URI = "copy:" + FileName; + QueueURI(Desc); + return; + } + + // Erase the file if it is a symlink so we can overwrite it + struct stat St; + if (lstat(DestFile.c_str(),&St) == 0) + { + if (S_ISLNK(St.st_mode) != 0) + RemoveFile("pkgAcqFile::Done", DestFile); + } + + // Symlink the file + if (symlink(FileName.c_str(),DestFile.c_str()) != 0) + { + _error->PushToStack(); + _error->Errno("pkgAcqFile::Done", "Symlinking file %s failed", DestFile.c_str()); + std::stringstream msg; + _error->DumpErrors(msg, GlobalError::DEBUG, false); + _error->RevertToStack(); + ErrorText = msg.str(); + Status = StatError; + Complete = false; + } + } +} + /*}}}*/ +void pkgAcqFile::Failed(string const &Message, pkgAcquire::MethodConfig const *const Cnf) /*{{{*/ +{ + // FIXME: Remove this pointless overload on next ABI break + Item::Failed(Message, Cnf); +} + /*}}}*/ +string pkgAcqFile::Custom600Headers() const /*{{{*/ +{ + if (IsIndexFile) + return "\nIndex-File: true"; + return ""; +} + /*}}}*/ +pkgAcqFile::~pkgAcqFile() {} + +void pkgAcqAuxFile::Failed(std::string const &Message, pkgAcquire::MethodConfig const *const Cnf) /*{{{*/ +{ + pkgAcqFile::Failed(Message, Cnf); + if (Status == StatIdle) + return; + if (RealFileExists(DestFile)) + Rename(DestFile, DestFile + ".FAILED"); + Worker->ReplyAux(Desc); +} + /*}}}*/ +void pkgAcqAuxFile::Done(std::string const &Message, HashStringList const &CalcHashes, /*{{{*/ + pkgAcquire::MethodConfig const *const Cnf) +{ + pkgAcqFile::Done(Message, CalcHashes, Cnf); + if (Status == StatDone) + Worker->ReplyAux(Desc); + else if (Status == StatAuthError || Status == StatError) + Worker->ReplyAux(Desc); +} + /*}}}*/ +std::string pkgAcqAuxFile::Custom600Headers() const /*{{{*/ +{ + if (MaximumSize == 0) + return pkgAcqFile::Custom600Headers(); + std::string maxsize; + strprintf(maxsize, "\nMaximum-Size: %llu", MaximumSize); + return pkgAcqFile::Custom600Headers().append(maxsize); +} + /*}}}*/ +void pkgAcqAuxFile::Finished() /*{{{*/ +{ + auto dirname = flCombine(_config->FindDir("Dir::State::lists"), "auxfiles/"); + if (APT::String::Startswith(DestFile, dirname)) + { + // the file is never returned by method requesting it, so fix up the permission now + if (FileExists(DestFile)) + { + ChangeOwnerAndPermissionOfFile("pkgAcqAuxFile", DestFile.c_str(), "root", ROOT_GROUP, 0644); + if (Status == StatDone) + return; + } + } + else + { + dirname = flNotFile(DestFile); + RemoveFile("pkgAcqAuxFile::Finished", DestFile); + RemoveFile("pkgAcqAuxFile::Finished", DestFile + ".FAILED"); + rmdir(dirname.c_str()); + } + DestFile.clear(); +} + /*}}}*/ +// GetAuxFileNameFromURI /*{{{*/ +static std::string GetAuxFileNameFromURIInLists(std::string const &uri) +{ + // check if we have write permission for our usual location. + auto const dirname = flCombine(_config->FindDir("Dir::State::lists"), "auxfiles/"); + char const * const filetag = ".apt-acquire-privs-test.XXXXXX"; + std::string const tmpfile_tpl = flCombine(dirname, filetag); + std::unique_ptr<char, decltype(std::free) *> tmpfile { strdup(tmpfile_tpl.c_str()), std::free }; + int const fd = mkstemp(tmpfile.get()); + if (fd == -1) + return ""; + RemoveFile("GetAuxFileNameFromURI", tmpfile.get()); + close(fd); + return flCombine(dirname, URItoFileName(uri)); +} +static std::string GetAuxFileNameFromURI(std::string const &uri) +{ + auto const lists = GetAuxFileNameFromURIInLists(uri); + if (lists.empty() == false) + return lists; + + std::string tmpdir_tpl; + strprintf(tmpdir_tpl, "%s/apt-auxfiles-XXXXXX", GetTempDir().c_str()); + std::unique_ptr<char, decltype(std::free) *> tmpdir { strndup(tmpdir_tpl.data(), tmpdir_tpl.length()), std::free }; + if (mkdtemp(tmpdir.get()) == nullptr) + { + _error->Errno("GetAuxFileNameFromURI", "mkdtemp of %s failed", tmpdir.get()); + return flCombine("/nonexistent/auxfiles/", URItoFileName(uri)); + } + chmod(tmpdir.get(), 0755); + auto const filename = flCombine(tmpdir.get(), URItoFileName(uri)); + _error->PushToStack(); + FileFd in(flCombine(flCombine(_config->FindDir("Dir::State::lists"), "auxfiles/"), URItoFileName(uri)), FileFd::ReadOnly); + if (in.IsOpen()) + { + FileFd out(filename, FileFd::WriteOnly | FileFd::Create | FileFd::Exclusive); + CopyFile(in, out); + ChangeOwnerAndPermissionOfFile("GetAuxFileNameFromURI", filename.c_str(), "root", ROOT_GROUP, 0644); + } + _error->RevertToStack(); + return filename; +} + /*}}}*/ +pkgAcqAuxFile::pkgAcqAuxFile(pkgAcquire::Item *const Owner, pkgAcquire::Worker *const Worker, + std::string const &ShortDesc, std::string const &Desc, std::string const &URI, + HashStringList const &Hashes, unsigned long long const MaximumSize) : pkgAcqFile(Owner->GetOwner(), URI, Hashes, Hashes.FileSize(), Desc, ShortDesc, "", GetAuxFileNameFromURI(URI), false), + Owner(Owner), Worker(Worker), MaximumSize(MaximumSize) +{ + /* very bad failures can happen while constructing which causes + us to hang as the aux request is never answered (e.g. method not available) + Ideally we catch failures earlier, but a safe guard can't hurt. */ + if (Status == pkgAcquire::Item::StatIdle || Status == pkgAcquire::Item::StatFetching) + return; + Failed(std::string("400 URI Failure\n") + + "URI: " + URI + "\n" + + "Filename: " + DestFile, + nullptr); +} +pkgAcqAuxFile::~pkgAcqAuxFile() {} diff --git a/apt-pkg/acquire-item.h b/apt-pkg/acquire-item.h new file mode 100644 index 0000000..70651d9 --- /dev/null +++ b/apt-pkg/acquire-item.h @@ -0,0 +1,1250 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Acquire Item - Item to acquire + + When an item is instantiated it will add it self to the local list in + the Owner Acquire class. Derived classes will then call QueueURI to + register all the URI's they wish to fetch at the initial moment. + + Three item classes are provided to provide functionality for + downloading of Index, Translation and Packages files. + + A Archive class is provided for downloading .deb files. It does Hash + checking and source location as well as a retry algorithm. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_ACQUIRE_ITEM_H +#define PKGLIB_ACQUIRE_ITEM_H + +#include <apt-pkg/acquire.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/weakptr.h> + +#include <map> +#include <string> +#include <unordered_map> +#include <vector> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/sourcelist.h> +#endif + +/** \addtogroup acquire + * @{ + * + * \file acquire-item.h + */ + +class pkgRecords; +class pkgSourceList; +class pkgAcqMetaClearSig; +class pkgAcqIndexMergeDiffs; +class metaIndex; + +class pkgAcquire::Item : public WeakPointable /*{{{*/ +/** \brief Represents the process by which a pkgAcquire object should + * retrieve a file or a collection of files. + * + * By convention, Item subclasses should insert themselves into the + * acquire queue when they are created by calling QueueURI(), and + * remove themselves by calling Dequeue() when either Done() or + * Failed() is invoked. Item objects are also responsible for + * notifying the download progress indicator (accessible via + * #Owner->Log) of their status. + * + * \see pkgAcquire + */ +{ + public: + + /** \brief The current status of this item. */ + enum ItemState + { + /** \brief The item is waiting to be downloaded. */ + StatIdle, + + /** \brief The item is currently being downloaded. */ + StatFetching, + + /** \brief The item has been successfully downloaded. */ + StatDone, + + /** \brief An error was encountered while downloading this + * item. + */ + StatError, + + /** \brief The item was downloaded but its authenticity could + * not be verified. + */ + StatAuthError, + + /** \brief The item was could not be downloaded because of + * a transient network error (e.g. network down) + */ + StatTransientNetworkError, + } Status; + + /** \brief Contains a textual description of the error encountered + * if #ItemState is #StatError or #StatAuthError. + */ + std::string ErrorText; + + /** \brief The size of the object to fetch. */ + unsigned long long FileSize; + + /** \brief How much of the object was already fetched. */ + unsigned long long PartialSize; + + /** \brief If not \b NULL, contains the name of a subprocess that + * is operating on this object (for instance, "gzip" or "gpgv"). + */ + APT_DEPRECATED_MSG("Use the std::string member ActiveSubprocess instead") const char *Mode; + + /** \brief contains the name of the subprocess that is operating on this object + * (for instance, "gzip", "rred" or "gpgv"). This is obsoleting #Mode from above + * as it can manage the lifetime of included string properly. */ + std::string ActiveSubprocess; + + /** \brief A client-supplied unique identifier. + * + * This field is initialized to 0; it is meant to be filled in by + * clients that wish to use it to uniquely identify items. + * + * APT progress reporting will store an ID there as shown in "Get:42 …" + */ + unsigned long ID; + + /** \brief If \b true, the entire object has been successfully fetched. + * + * Subclasses should set this to \b true when appropriate. + */ + bool Complete; + + /** \brief If \b true, the URI of this object is "local". + * + * The only effect of this field is to exclude the object from the + * download progress indicator's overall statistics. + */ + bool Local; + + std::string UsedMirror; + + /** \brief The number of fetch queues into which this item has been + * inserted. + * + * There is one queue for each source from which an item could be + * downloaded. + * + * \sa pkgAcquire + */ + unsigned int QueueCounter; + + /** \brief The number of additional fetch items that are expected + * once this item is done. + * + * Some items like pkgAcqMeta{Index,Sig} will queue additional + * items. This variable can be set by the methods if it knows + * in advance how many items to expect to get a more accurate + * progress. + */ + unsigned int ExpectedAdditionalItems; + + /** \brief The name of the file into which the retrieved object + * will be written. + */ + std::string DestFile; + + /** \brief Invoked by the acquire worker when the object couldn't + * be fetched. + * + * This is a branch of the continuation of the fetch process. + * + * \param Message An RFC822-formatted message from the acquire + * method describing what went wrong. Use LookupTag() to parse + * it. + * + * \param Cnf The method via which the worker tried to fetch this object. + * + * \sa pkgAcqMethod + */ + virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf); + APT_HIDDEN void FailMessage(std::string const &Message); + + /** \brief Invoked by the acquire worker to check if the successfully + * fetched object is also the objected we wanted to have. + * + * Note that the object might \e not have been written to + * DestFile; check for the presence of an Alt-Filename entry in + * Message to find the file to which it was really written. + * + * This is called before Done is called and can prevent it by returning + * \b false which will result in Failed being called instead. + * + * You should prefer to use this method over calling Failed() from Done() + * as this has e.g. the wrong progress reporting. + * + * \param Message Data from the acquire method. Use LookupTag() + * to parse it. + * \param Cnf The method via which the object was fetched. + * + * \sa pkgAcqMethod + */ + virtual bool VerifyDone(std::string const &Message, + pkgAcquire::MethodConfig const * const Cnf); + + /** \brief Invoked by the acquire worker when the object was + * fetched successfully. + * + * Note that the object might \e not have been written to + * DestFile; check for the presence of an Alt-Filename entry in + * Message to find the file to which it was really written. + * + * Done is often used to switch from one stage of the processing + * to the next (e.g. fetching, unpacking, copying). It is one + * branch of the continuation of the fetch process. + * + * \param Message Data from the acquire method. Use LookupTag() + * to parse it. + * \param Hashes The HashSums of the object that was fetched. + * \param Cnf The method via which the object was fetched. + * + * \sa pkgAcqMethod + */ + virtual void Done(std::string const &Message, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cnf); + + /** \brief Invoked when the worker starts to fetch this object. + * + * \param Message RFC822-formatted data from the worker process. + * Use LookupTag() to parse it. + * + * \param Hashes The expected hashes of the object being fetched. + * + * \sa pkgAcqMethod + */ + virtual void Start(std::string const &Message, unsigned long long const Size); + + /** \brief Custom headers to be sent to the fetch process. + * + * \return a string containing RFC822-style headers that are to be + * inserted into the 600 URI Acquire message sent to the fetch + * subprocess. The headers are inserted after a newline-less + * line, so they should (if nonempty) have a leading newline and + * no trailing newline. + */ + virtual std::string Custom600Headers() const; + // Retries should really be a member of the Item, but can't be for ABI reasons + APT_HIDDEN unsigned int &ModifyRetries(); + // this is more a hack than a proper external interface, hence hidden + APT_HIDDEN std::unordered_map<std::string, std::string> &ModifyCustomFields(); + // this isn't the super nicest interface either… + APT_HIDDEN bool PopAlternativeURI(std::string &NewURI); + APT_HIDDEN bool IsGoodAlternativeURI(std::string const &AltUri) const; + APT_HIDDEN void PushAlternativeURI(std::string &&NewURI, std::unordered_map<std::string, std::string> &&fields, bool const at_the_back); + APT_HIDDEN void RemoveAlternativeSite(std::string &&OldSite); + + /** \brief A "descriptive" URI-like string. + * + * \return a URI that should be used to describe what is being fetched. + */ + virtual std::string DescURI() const = 0; + /** \brief Short item description. + * + * \return a brief description of the object being fetched. + */ + virtual std::string ShortDesc() const; + + /** \brief Invoked by the worker when the download is completely done. */ + virtual void Finished(); + + /** \return HashSums the DestFile is supposed to have in this stage */ + virtual HashStringList GetExpectedHashes() const = 0; + /** \return the 'best' hash for display proposes like --print-uris */ + std::string HashSum() const; + + /** \return if having no hashes is a hard failure or not + * + * Idealy this is always \b true for every subclass, but thanks to + * historical grow we don't have hashes for all files in all cases + * in all steps, so it is slightly more complicated than it should be. + */ + virtual bool HashesRequired() const { return true; } + + /** \return the acquire process with which this item is associated. */ + pkgAcquire *GetOwner() const; + pkgAcquire::ItemDesc &GetItemDesc(); + + /** \return \b true if this object is being fetched from a trusted source. */ + virtual bool IsTrusted() const; + + /** \brief Report mirror problem + * + * This allows reporting mirror failures back to a centralized + * server. The apt-report-mirror-failure script is called for this + * + * \param FailCode A short failure string that is send + */ + APT_DEPRECATED_MSG("Item::Failed does this for you") void ReportMirrorFailure(std::string const &FailCode); + + /** \brief Set the name of the current active subprocess + * + * See also #ActiveSubprocess + */ + void SetActiveSubprocess(std::string const &subprocess); + + /** \brief Initialize an item. + * + * Adds the item to the list of items known to the acquire + * process, but does not place it into any fetch queues (you must + * manually invoke QueueURI() to do so). + * + * \param Owner The new owner of this item. + */ + explicit Item(pkgAcquire * const Owner); + + /** \brief Remove this item from its owner's queue by invoking + * pkgAcquire::Remove. + */ + virtual ~Item(); + + bool APT_HIDDEN IsRedirectionLoop(std::string const &NewURI); + /** \brief The priority of the item, used for queuing */ + int APT_HIDDEN Priority(); + + protected: + /** \brief The acquire object with which this item is associated. */ + pkgAcquire * const Owner; + + /** \brief The item that is currently being downloaded. */ + pkgAcquire::ItemDesc Desc; + + enum RenameOnErrorState { + HashSumMismatch, + SizeMismatch, + InvalidFormat, + SignatureError, + NotClearsigned, + MaximumSizeExceeded, + PDiffError, + }; + + /** \brief Rename failed file and set error + * + * \param state respresenting the error we encountered + */ + bool RenameOnError(RenameOnErrorState const state); + + /** \brief Insert this item into its owner's queue. + * + * The method is designed to check if the request would end + * in an IMSHit and if it determines that it would, it isn't + * queueing the Item and instead sets it to completion instantly. + * + * \param Item Metadata about this item (its URI and + * description). + * \return true if the item was inserted, false if IMSHit was detected + */ + virtual bool QueueURI(ItemDesc &Item); + + /** \brief Remove this item from its owner's queue. */ + void Dequeue(); + + /** \brief Rename a file without modifying its timestamp. + * + * Many item methods call this as their final action. + * + * \param From The file to be renamed. + * + * \param To The new name of \a From. If \a To exists it will be + * overwritten. If \a From and \a To are equal nothing happens. + */ + bool Rename(std::string const &From, std::string const &To); + + /** \brief Get the full pathname of the final file for the current URI */ + virtual std::string GetFinalFilename() const; + + private: + class Private; + Private * const d; + + friend class pkgAcqMetaBase; + friend class pkgAcqMetaClearSig; +}; + /*}}}*/ +class APT_HIDDEN pkgAcqTransactionItem: public pkgAcquire::Item /*{{{*/ +/** \brief baseclass for the indexes files to manage them all together */ +{ + void * const d; + protected: + HashStringList GetExpectedHashesFor(std::string const &MetaKey) const; + + bool QueueURI(pkgAcquire::ItemDesc &Item) APT_OVERRIDE; + + public: + IndexTarget const Target; + + /** \brief storge name until a transaction is finished */ + std::string PartialFile; + + /** \brief TransactionManager */ + pkgAcqMetaClearSig * const TransactionManager; + + enum TransactionStates { + TransactionStarted, + TransactionCommit, + TransactionAbort, + }; + virtual bool TransactionState(TransactionStates const state); + + virtual std::string DescURI() const APT_OVERRIDE { return Target.URI; } + virtual HashStringList GetExpectedHashes() const APT_OVERRIDE; + virtual std::string GetMetaKey() const; + virtual bool HashesRequired() const APT_OVERRIDE; + virtual bool AcquireByHash() const; + + pkgAcqTransactionItem(pkgAcquire * const Owner, pkgAcqMetaClearSig * const TransactionManager, IndexTarget const &Target) APT_NONNULL(2, 3); + virtual ~pkgAcqTransactionItem(); + + friend class pkgAcqMetaBase; + friend class pkgAcqMetaClearSig; +}; + /*}}}*/ +class APT_HIDDEN pkgAcqMetaBase : public pkgAcqTransactionItem /*{{{*/ +/** \brief the manager of a transaction */ +{ + void * const d; + protected: + std::vector<pkgAcqTransactionItem*> Transaction; + + /** \brief If \b true, the index's signature is currently being verified. + */ + bool AuthPass; + + /** \brief Called when a file is finished being retrieved. + * + * If the file was not downloaded to DestFile, a copy process is + * set up to copy it to DestFile; otherwise, Complete is set to \b + * true and the file is moved to its final location. + * + * \param Message The message block received from the fetch + * subprocess. + */ + bool CheckDownloadDone(pkgAcqTransactionItem * const I, const std::string &Message, HashStringList const &Hashes) const; + + /** \brief Queue the downloaded Signature for verification */ + void QueueForSignatureVerify(pkgAcqTransactionItem * const I, std::string const &File, std::string const &Signature); + + virtual std::string Custom600Headers() const APT_OVERRIDE; + + /** \brief Called when authentication succeeded. + * + * Sanity-checks the authenticated file, queues up the individual + * index files for download, and saves the signature in the lists + * directory next to the authenticated list file. + * + * \param Message The message block received from the fetch + * subprocess. + * \param Cnf The method and its configuration which handled the request + */ + bool CheckAuthDone(std::string const &Message, pkgAcquire::MethodConfig const *const Cnf); + + /** Check if the current item should fail at this point */ + bool CheckStopAuthentication(pkgAcquire::Item * const I, const std::string &Message); + + /** \brief Check that the release file is a release file for the + * correct distribution. + * + * \return \b true if no fatal errors were encountered. + */ + bool VerifyVendor(std::string const &Message); + + virtual bool TransactionState(TransactionStates const state) APT_OVERRIDE; + + public: + // This refers more to the Transaction-Manager than the actual file + bool IMSHit; + TransactionStates State; + std::string BaseURI; + + virtual bool QueueURI(pkgAcquire::ItemDesc &Item) APT_OVERRIDE; + virtual HashStringList GetExpectedHashes() const APT_OVERRIDE; + virtual bool HashesRequired() const APT_OVERRIDE; + + // transaction code + void Add(pkgAcqTransactionItem * const I); + void AbortTransaction(); + bool TransactionHasError() const; + void CommitTransaction(); + + /** \brief Stage (queue) a copy action when the transaction is committed + */ + void TransactionStageCopy(pkgAcqTransactionItem * const I, + const std::string &From, + const std::string &To); + /** \brief Stage (queue) a removal action when the transaction is committed + */ + void TransactionStageRemoval(pkgAcqTransactionItem * const I, const std::string &FinalFile); + + /** \brief Get the full pathname of the final file for the current URI */ + virtual std::string GetFinalFilename() const APT_OVERRIDE; + + pkgAcqMetaBase(pkgAcquire * const Owner, pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &DataTarget) APT_NONNULL(2, 3); + virtual ~pkgAcqMetaBase(); +}; + /*}}}*/ +/** \brief An item that is responsible for downloading the meta-index {{{ + * file (i.e., Release) itself and verifying its signature. + * + * Once the download and verification are complete, the downloads of + * the individual index files are queued up using pkgAcqDiffIndex. + * If the meta-index file had a valid signature, the expected hashsums + * of the index files will be the md5sums listed in the meta-index; + * otherwise, the expected hashsums will be "" (causing the + * authentication of the index files to be bypassed). + */ +class APT_HIDDEN pkgAcqMetaIndex : public pkgAcqMetaBase +{ + void * const d; + protected: + IndexTarget const DetachedSigTarget; + + /** \brief delayed constructor */ + void Init(std::string const &URIDesc, std::string const &ShortDesc); + + public: + virtual std::string DescURI() const APT_OVERRIDE; + + // Specialized action members + virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual void Done(std::string const &Message, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + + /** \brief Create a new pkgAcqMetaIndex. */ + pkgAcqMetaIndex(pkgAcquire * const Owner, pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &DataTarget, IndexTarget const &DetachedSigTarget) APT_NONNULL(2, 3); + virtual ~pkgAcqMetaIndex(); + + friend class pkgAcqMetaSig; +}; + /*}}}*/ +/** \brief An acquire item that downloads the detached signature {{{ + * of a meta-index (Release) file, then queues up the release + * file itself. + * + * \todo Why protected members? + * + * \sa pkgAcqMetaIndex + */ +class APT_HIDDEN pkgAcqMetaSig : public pkgAcqTransactionItem +{ + void * const d; + + pkgAcqMetaIndex * const MetaIndex; + + /** \brief The file we use to verify the MetaIndexFile with (not always set!) */ + std::string MetaIndexFileSignature; + + protected: + + /** \brief Get the full pathname of the final file for the current URI */ + virtual std::string GetFinalFilename() const APT_OVERRIDE; + + public: + virtual bool HashesRequired() const APT_OVERRIDE { return false; } + + // Specialized action members + virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual void Done(std::string const &Message, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual std::string Custom600Headers() const APT_OVERRIDE; + + /** \brief Create a new pkgAcqMetaSig. */ + pkgAcqMetaSig(pkgAcquire * const Owner, pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &Target, pkgAcqMetaIndex * const MetaIndex) APT_NONNULL(2, 3, 5); + virtual ~pkgAcqMetaSig(); +}; + /*}}}*/ +/** \brief An item responsible for downloading clearsigned metaindexes {{{*/ +class APT_HIDDEN pkgAcqMetaClearSig : public pkgAcqMetaIndex +{ + void * const d; + IndexTarget const DetachedDataTarget; + + public: + /** \brief A package-system-specific parser for the meta-index file. */ + metaIndex *MetaIndexParser; + metaIndex *LastMetaIndexParser; + + virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual std::string Custom600Headers() const APT_OVERRIDE; + virtual bool VerifyDone(std::string const &Message, pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual void Done(std::string const &Message, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual void Finished() APT_OVERRIDE; + + /** \brief Starts downloading the individual index files. + * + * \param verify If \b true, only indices whose expected hashsum + * can be determined from the meta-index will be downloaded, and + * the hashsums of indices will be checked (reporting + * #StatAuthError if there is a mismatch). If verify is \b false, + * no hashsum checking will be performed. + */ + void QueueIndexes(bool const verify); + + /** \brief Create a new pkgAcqMetaClearSig. */ + pkgAcqMetaClearSig(pkgAcquire * const Owner, + IndexTarget const &ClearsignedTarget, + IndexTarget const &DetachedDataTarget, + IndexTarget const &DetachedSigTarget, + metaIndex * const MetaIndexParser); + virtual ~pkgAcqMetaClearSig(); +}; + /*}}}*/ +/** \brief Common base class for all classes that deal with fetching indexes {{{*/ +class APT_HIDDEN pkgAcqBaseIndex : public pkgAcqTransactionItem +{ + void * const d; + + public: + /** \brief Get the full pathname of the final file for the current URI */ + virtual std::string GetFinalFilename() const APT_OVERRIDE; + virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + + pkgAcqBaseIndex(pkgAcquire * const Owner, pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &Target) APT_NONNULL(2, 3); + virtual ~pkgAcqBaseIndex(); +}; + /*}}}*/ +/** \brief An acquire item that is responsible for fetching an index {{{ + * file (e.g., Packages or Sources). + * + * \sa pkgAcqDiffIndex, pkgAcqIndexDiffs, pkgAcqIndexTrans + * + * \todo Why does pkgAcqIndex have protected members? + */ +class APT_HIDDEN pkgAcqIndex : public pkgAcqBaseIndex +{ + void * const d; + + protected: + + /** \brief The stages the method goes through + * + * The method first downloads the indexfile, then its decompressed (or + * copied) and verified + */ + enum AllStages { + STAGE_DOWNLOAD, + STAGE_DECOMPRESS_AND_VERIFY, + }; + AllStages Stage; + + /** \brief Handle what needs to be done when the download is done */ + void StageDownloadDone(std::string const &Message); + + /** \brief Handle what needs to be done when the decompression/copy is + * done + */ + void StageDecompressDone(); + + /** \brief If \b set, this partially downloaded file will be + * removed when the download completes. + */ + std::string EraseFileName; + + /** \brief The compression-related file extensions that are being + * added to the downloaded file one by one if first fails (e.g., "gz bz2"). + */ + std::string CompressionExtensions; + + /** \brief The actual compression extension currently used */ + std::string CurrentCompressionExtension; + + /** \brief Do the changes needed to fetch via AptByHash (if needed) */ + void InitByHashIfNeeded(); + + /** \brief Get the full pathname of the final file for the current URI */ + virtual std::string GetFinalFilename() const APT_OVERRIDE; + + virtual bool TransactionState(TransactionStates const state) APT_OVERRIDE; + + public: + // Specialized action members + virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual void Done(std::string const &Message, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual std::string Custom600Headers() const APT_OVERRIDE; + virtual std::string DescURI() const APT_OVERRIDE {return Desc.URI;}; + virtual std::string GetMetaKey() const APT_OVERRIDE; + + pkgAcqIndex(pkgAcquire * const Owner, pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &Target, bool const Derived = false) APT_NONNULL(2, 3); + virtual ~pkgAcqIndex(); + + protected: + APT_HIDDEN void Init(std::string const &URI, std::string const &URIDesc, + std::string const &ShortDesc); + APT_HIDDEN bool CommonFailed(std::string const &TargetURI, + std::string const &Message, pkgAcquire::MethodConfig const *const Cnf); +}; + /*}}}*/ +struct APT_HIDDEN DiffInfo { /*{{{*/ + /** The filename of the diff. */ + std::string file; + + /** The hashes of the file after the diff is applied */ + HashStringList result_hashes; + + /** The hashes of the diff */ + HashStringList patch_hashes; + + /** The hashes of the compressed diff */ + HashStringList download_hashes; +}; + /*}}}*/ +/** \brief An item that is responsible for fetching an index file of {{{ + * package list diffs and starting the package list's download. + * + * This item downloads the Index file and parses it, then enqueues + * additional downloads of either the individual patches (using + * pkgAcqIndexDiffs) or the entire Packages file (using pkgAcqIndex). + * + * \sa pkgAcqIndexDiffs, pkgAcqIndex + */ +class APT_HIDDEN pkgAcqDiffIndex : public pkgAcqIndex +{ + void * const d; + std::vector<pkgAcqIndexMergeDiffs*> * diffs; + std::vector<DiffInfo> available_patches; + bool pdiff_merge; + + protected: + /** \brief If \b true, debugging information will be written to std::clog. */ + bool Debug; + + /** \brief Get the full pathname of the final file for the current URI */ + virtual std::string GetFinalFilename() const APT_OVERRIDE; + + virtual bool QueueURI(pkgAcquire::ItemDesc &Item) APT_OVERRIDE; + + virtual bool TransactionState(TransactionStates const state) APT_OVERRIDE; + public: + // Specialized action members + virtual void Failed(std::string const &Message, pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual bool VerifyDone(std::string const &Message, pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual void Done(std::string const &Message, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual std::string DescURI() const APT_OVERRIDE {return Target.URI + "Index";}; + virtual std::string GetMetaKey() const APT_OVERRIDE; + + /** \brief Parse the Index file for a set of Packages diffs. + * + * Parses the Index file and creates additional download items as + * necessary. + * + * \param IndexDiffFile The name of the Index file. + * + * \return \b true if the Index file was successfully parsed, \b + * false otherwise. + */ + bool ParseDiffIndex(std::string const &IndexDiffFile); + + /** \brief Create a new pkgAcqDiffIndex. + * + * \param Owner The Acquire object that owns this item. + * + * \param URI The URI of the list file to download. + * + * \param URIDesc A long description of the list file to download. + * + * \param ShortDesc A short description of the list file to download. + */ + pkgAcqDiffIndex(pkgAcquire * const Owner, pkgAcqMetaClearSig * const TransactionManager, + IndexTarget const &Target) APT_NONNULL(2, 3); + virtual ~pkgAcqDiffIndex(); + private: + APT_HIDDEN void QueueOnIMSHit() const; +}; + /*}}}*/ +/** \brief An item that is responsible for fetching client-merge patches {{{ + * that need to be applied to a given package index file. + * + * Instead of downloading and applying each patch one by one like its + * sister #pkgAcqIndexDiffs this class will download all patches at once + * and call rred with all the patches downloaded once. Rred will then + * merge and apply them in one go, which should be a lot faster – but is + * incompatible with server-based merges of patches like reprepro can do. + * + * \sa pkgAcqDiffIndex, pkgAcqIndex + */ +class APT_HIDDEN pkgAcqIndexMergeDiffs : public pkgAcqBaseIndex +{ + protected: + + /** \brief If \b true, debugging output will be written to + * std::clog. + */ + bool Debug; + + /** \brief information about the current patch */ + struct DiffInfo const patch; + + /** \brief list of all download items for the patches */ + std::vector<pkgAcqIndexMergeDiffs*> const * const allPatches; + + /** The current status of this patch. */ + enum DiffState + { + /** \brief The diff is currently being fetched. */ + StateFetchDiff, + + /** \brief The diff is currently being applied. */ + StateApplyDiff, + + /** \brief the work with this diff is done */ + StateDoneDiff, + + /** \brief something bad happened and fallback was triggered */ + StateErrorDiff + } State; + + public: + /** \brief Called when the patch file failed to be downloaded. + * + * This method will fall back to downloading the whole index file + * outright; its arguments are ignored. + */ + virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual void Done(std::string const &Message, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual std::string Custom600Headers() const APT_OVERRIDE; + virtual std::string DescURI() const APT_OVERRIDE {return Target.URI + "Index";}; + virtual HashStringList GetExpectedHashes() const APT_OVERRIDE; + virtual bool HashesRequired() const APT_OVERRIDE; + virtual bool AcquireByHash() const APT_OVERRIDE; + + /** \brief Create an index merge-diff item. + * + * \param Owner The pkgAcquire object that owns this item. + * \param TransactionManager responsible for this item + * \param Target we intend to built via pdiff patching + * \param baseURI is the URI used for the Index, but stripped down to Target + * \param DiffInfo of the patch in question + * \param patch contains infos about the patch this item is supposed + * to download which were read from the index + * \param allPatches contains all related items so that each item can + * check if it was the last one to complete the download step + */ + pkgAcqIndexMergeDiffs(pkgAcquire *const Owner, pkgAcqMetaClearSig *const TransactionManager, + IndexTarget const &Target, DiffInfo const &patch, + std::vector<pkgAcqIndexMergeDiffs *> const *const allPatches) APT_NONNULL(2, 3, 6); + virtual ~pkgAcqIndexMergeDiffs(); +}; + /*}}}*/ +/** \brief An item that is responsible for fetching server-merge patches {{{ + * that need to be applied to a given package index file. + * + * After downloading and applying a single patch, this item will + * enqueue a new pkgAcqIndexDiffs to download and apply the remaining + * patches. If no patch can be found that applies to an intermediate + * file or if one of the patches cannot be downloaded, falls back to + * downloading the entire package index file using pkgAcqIndex. + * + * \sa pkgAcqDiffIndex, pkgAcqIndex + */ +class APT_HIDDEN pkgAcqIndexDiffs : public pkgAcqBaseIndex +{ + private: + + /** \brief Queue up the next diff download. + * + * Search for the next available diff that applies to the file + * that currently exists on disk, and enqueue it by calling + * QueueURI(). + * + * \return \b true if an applicable diff was found, \b false + * otherwise. + */ + APT_HIDDEN bool QueueNextDiff(); + + /** \brief Handle tasks that must be performed after the item + * finishes downloading. + * + * Dequeues the item and checks the resulting file's hashsums + * against ExpectedHashes after the last patch was applied. + * There is no need to check the md5/sha1 after a "normal" + * patch because QueueNextDiff() will check the sha1 later. + * + * \param allDone If \b true, the file was entirely reconstructed, + * and its md5sum is verified. + */ + APT_HIDDEN void Finish(bool const allDone=false); + + protected: + + /** \brief If \b true, debugging output will be written to + * std::clog. + */ + bool Debug; + + /** The patches that remain to be downloaded, including the patch + * being downloaded right now. This list should be ordered so + * that each diff appears before any diff that depends on it. + * + * \todo These are indexed by sha1sum; why not use some sort of + * dictionary instead of relying on ordering and stripping them + * off the front? + */ + std::vector<DiffInfo> available_patches; + + /** The current status of this patch. */ + enum DiffState + { + /** \brief The diff is currently being fetched. */ + StateFetchDiff, + + /** \brief The diff is currently being applied. */ + StateApplyDiff + } State; + + public: + + /** \brief Called when the patch file failed to be downloaded. + * + * This method will fall back to downloading the whole index file + * outright; its arguments are ignored. + */ + virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + + virtual void Done(std::string const &Message, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual std::string Custom600Headers() const APT_OVERRIDE; + virtual std::string DescURI() const APT_OVERRIDE {return Target.URI + "IndexDiffs";}; + virtual HashStringList GetExpectedHashes() const APT_OVERRIDE; + virtual bool HashesRequired() const APT_OVERRIDE; + virtual bool AcquireByHash() const APT_OVERRIDE; + + /** \brief Create an index diff item. + * + * After filling in its basic fields, this invokes Finish(true) if + * \a diffs is empty, or QueueNextDiff() otherwise. + * + * \param Owner The pkgAcquire object that owns this item. + * \param TransactionManager responsible for this item + * \param Target we want to built via pdiff patching + * \param baseURI is the URI used for the Index, but stripped down to Target + * \param diffs The remaining diffs from the index of diffs. They + * should be ordered so that each diff appears before any diff + * that depends on it. + */ + pkgAcqIndexDiffs(pkgAcquire *const Owner, pkgAcqMetaClearSig *const TransactionManager, + IndexTarget const &Target, + std::vector<DiffInfo> const &diffs = std::vector<DiffInfo>()) APT_NONNULL(2, 3); + virtual ~pkgAcqIndexDiffs(); +}; + /*}}}*/ +/** \brief An item that is responsible for fetching a package file. {{{ + * + * If the package file already exists in the cache, nothing will be + * done. + */ +class pkgAcqArchive : public pkgAcquire::Item +{ + void * const d; + + bool LocalSource; + HashStringList ExpectedHashes; + + protected: + /** \brief The package version being fetched. */ + pkgCache::VerIterator Version; + + /** \brief The list of sources from which to pick archives to + * download this package from. + */ + pkgSourceList *Sources; + + /** \brief A package records object, used to look up the file + * corresponding to each version of the package. + */ + pkgRecords *Recs; + + /** \brief A location in which the actual filename of the package + * should be stored. + */ + std::string &StoreFilename; + + /** \brief The next file for this version to try to download. */ + APT_DEPRECATED_MSG("Unused member") + pkgCache::VerFileIterator Vf; + + /** \brief How many (more) times to try to find a new source from + * which to download this package version if it fails. + * + * Set from Acquire::Retries. + */ + APT_DEPRECATED_MSG("Unused member. See pkgAcqItem::Retries.") + unsigned int Retries; + + /** \brief \b true if this version file is being downloaded from a + * trusted source. + */ + bool Trusted; + + /** \brief Queue up the next available file for this version. */ + bool QueueNext(); + + /** \brief Get the full pathname of the final file for the current URI */ + virtual std::string GetFinalFilename() const APT_OVERRIDE; + + public: + + virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual void Done(std::string const &Message, HashStringList const &Hashes, + pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual std::string DescURI() const APT_OVERRIDE; + virtual std::string ShortDesc() const APT_OVERRIDE; + virtual void Finished() APT_OVERRIDE; + virtual bool IsTrusted() const APT_OVERRIDE; + virtual HashStringList GetExpectedHashes() const APT_OVERRIDE; + virtual bool HashesRequired() const APT_OVERRIDE; + + /** \brief Create a new pkgAcqArchive. + * + * \param Owner The pkgAcquire object with which this item is + * associated. + * + * \param Sources The sources from which to download version + * files. + * + * \param Recs A package records object, used to look up the file + * corresponding to each version of the package. + * + * \param Version The package version to download. + * + * \param[out] StoreFilename A location in which the actual filename of + * the package should be stored. It will be set to a guessed + * basename in the constructor, and filled in with a fully + * qualified filename once the download finishes. + */ + pkgAcqArchive(pkgAcquire * const Owner,pkgSourceList * const Sources, + pkgRecords * const Recs,pkgCache::VerIterator const &Version, + std::string &StoreFilename); + virtual ~pkgAcqArchive(); +}; + /*}}}*/ +/** \brief Retrieve the changelog for the given version {{{ + * + * Downloads the changelog to a temporary file it will also remove again + * while it is deconstructed or downloads it to a named location. + */ +class pkgAcqChangelog : public pkgAcquire::Item +{ + class Private; + Private * const d; + std::string TemporaryDirectory; + std::string const SrcName; + std::string const SrcVersion; + + public: + // we will never have hashes for changelogs. + // If you need verified ones, download the deb and extract the changelog. + virtual HashStringList GetExpectedHashes() const APT_OVERRIDE { return HashStringList(); } + virtual bool HashesRequired() const APT_OVERRIDE { return false; } + + // Specialized action members + virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual void Done(std::string const &Message, HashStringList const &CalcHashes, + pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual std::string DescURI() const APT_OVERRIDE {return Desc.URI;}; + + /** returns the URI to the changelog of this version + * + * @param Ver is the version to get the changelog for + * @return the URI which will be used to acquire the changelog + */ + static std::string URI(pkgCache::VerIterator const &Ver); + + /** returns the URI to the changelog of this version + * + * \param Rls is the Release file the package comes from + * \param Component in which the package resides, can be empty + * \param SrcName is the source package name + * \param SrcVersion is the source package version + * @return the URI which will be used to acquire the changelog + */ + static std::string URI(pkgCache::RlsFileIterator const &Rls, + char const * const Component, char const * const SrcName, + char const * const SrcVersion); + + /** returns the URI to the changelog of this version + * + * \param Template URI where @CHANGEPATH@ has to be filled in + * \param Component in which the package resides, can be empty + * \param SrcName is the source package name + * \param SrcVersion is the source package version + * @return the URI which will be used to acquire the changelog + */ + static std::string URI(std::string const &Template, + char const * const Component, char const * const SrcName, + char const * const SrcVersion); + + /** returns the URI template for this release file + * + * \param Rls is a Release file + * @return the URI template to use for this release file + */ + static std::string URITemplate(pkgCache::RlsFileIterator const &Rls); + + /** \brief Create a new pkgAcqChangelog object. + * + * \param Owner The pkgAcquire object with which this object is + * associated. + * \param Ver is the version to get the changelog for + * \param DestDir The directory the file should be downloaded into. + * Will be an autocreated (and cleaned up) temporary directory if not set. + * \param DestFilename The filename the file should have in #DestDir + * Defaults to sourcepackagename.changelog if not set. + */ + pkgAcqChangelog(pkgAcquire * const Owner, pkgCache::VerIterator const &Ver, + std::string const &DestDir="", std::string const &DestFilename=""); + + /** \brief Create a new pkgAcqChangelog object. + * + * \param Owner The pkgAcquire object with which this object is + * associated. + * \param Rls is the Release file the package comes from + * \param Component in which the package resides, can be empty + * \param SrcName is the source package name + * \param SrcVersion is the source package version + * \param DestDir The directory the file should be downloaded into. + * Will be an autocreated (and cleaned up) temporary directory if not set. + * \param DestFilename The filename the file should have in #DestDir + * Defaults to sourcepackagename.changelog if not set. + */ + pkgAcqChangelog(pkgAcquire * const Owner, pkgCache::RlsFileIterator const &Rls, + char const * const Component, char const * const SrcName, char const * const SrcVersion, + std::string const &DestDir="", std::string const &DestFilename=""); + + /** \brief Create a new pkgAcqChangelog object. + * + * \param Owner The pkgAcquire object with which this object is + * associated. + * \param URI is to be used to get the changelog + * \param SrcName is the source package name + * \param SrcVersion is the source package version + * \param DestDir The directory the file should be downloaded into. + * Will be an autocreated (and cleaned up) temporary directory if not set. + * \param DestFilename The filename the file should have in #DestDir + * Defaults to sourcepackagename.changelog if not set. + */ + pkgAcqChangelog(pkgAcquire * const Owner, std::string const &URI, + char const * const SrcName, char const * const SrcVersion, + std::string const &DestDir="", std::string const &DestFilename=""); + + virtual ~pkgAcqChangelog(); + +private: + APT_HIDDEN void Init(std::string const &DestDir, std::string const &DestFilename); +}; + /*}}}*/ +/** \brief Retrieve an arbitrary file to the current directory. {{{ + * + * The file is retrieved even if it is accessed via a URL type that + * normally is a NOP, such as "file". If the download fails, the + * partial file is renamed to get a ".FAILED" extension. + */ +class pkgAcqFile : public pkgAcquire::Item +{ + void * const d; + + /** \brief How many times to retry the download, set from + * Acquire::Retries. + */ + APT_DEPRECATED_MSG("Unused member. See pkgAcqItem::Retries.") + unsigned int Retries; + + /** \brief Should this file be considered a index file */ + bool IsIndexFile; + + HashStringList const ExpectedHashes; + public: + virtual HashStringList GetExpectedHashes() const APT_OVERRIDE; + virtual bool HashesRequired() const APT_OVERRIDE; + + // Specialized action members + virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual void Done(std::string const &Message, HashStringList const &CalcHashes, + pkgAcquire::MethodConfig const * const Cnf) APT_OVERRIDE; + virtual std::string DescURI() const APT_OVERRIDE {return Desc.URI;}; + virtual std::string Custom600Headers() const APT_OVERRIDE; + + /** \brief Create a new pkgAcqFile object. + * + * \param Owner The pkgAcquire object with which this object is + * associated. + * + * \param URI The URI to download. + * + * \param Hashes The hashsums of the file to download, if they are known; + * otherwise empty list. + * + * \param Size The size of the file to download, if it is known; + * otherwise 0. + * + * \param Desc A description of the file being downloaded. + * + * \param ShortDesc A brief description of the file being + * downloaded. + * + * \param DestDir The directory the file should be downloaded into. + * + * \param DestFilename The filename+path the file is downloaded to. + * + * \param IsIndexFile The file is considered a IndexFile and cache-control + * headers like "cache-control: max-age=0" are send + * + * If DestFilename is empty, download to DestDir/\<basename\> if + * DestDir is non-empty, $CWD/\<basename\> otherwise. If + * DestFilename is NOT empty, DestDir is ignored and DestFilename + * is the absolute name to which the file should be downloaded. + */ + + pkgAcqFile(pkgAcquire * const Owner, std::string const &URI, HashStringList const &Hashes, unsigned long long const Size, + std::string const &Desc, std::string const &ShortDesc, + std::string const &DestDir="", std::string const &DestFilename="", + bool const IsIndexFile=false); + virtual ~pkgAcqFile(); +}; + /*}}}*/ +class APT_HIDDEN pkgAcqAuxFile : public pkgAcqFile /*{{{*/ +{ + pkgAcquire::Item *const Owner; + pkgAcquire::Worker *const Worker; + unsigned long long MaximumSize; + + public: + virtual void Failed(std::string const &Message, pkgAcquire::MethodConfig const *const Cnf) APT_OVERRIDE; + virtual void Done(std::string const &Message, HashStringList const &CalcHashes, + pkgAcquire::MethodConfig const *const Cnf) APT_OVERRIDE; + virtual std::string Custom600Headers() const APT_OVERRIDE; + virtual void Finished() APT_OVERRIDE; + + pkgAcqAuxFile(pkgAcquire::Item *const Owner, pkgAcquire::Worker *const Worker, + std::string const &ShortDesc, std::string const &Desc, std::string const &URI, + HashStringList const &Hashes, unsigned long long const MaximumSize); + virtual ~pkgAcqAuxFile(); +}; + /*}}}*/ +/** @} */ + +#endif diff --git a/apt-pkg/acquire-method.cc b/apt-pkg/acquire-method.cc new file mode 100644 index 0000000..ae5ae4a --- /dev/null +++ b/apt-pkg/acquire-method.cc @@ -0,0 +1,565 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Acquire Method + + This is a skeleton class that implements most of the functionality + of a method and some useful functions to make method implementation + simpler. The methods all derive this and specialize it. The most + complex implementation is the http method which needs to provide + pipelining, it runs the message engine at the same time it is + downloading files.. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/acquire-method.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/md5.h> +#include <apt-pkg/sha1.h> +#include <apt-pkg/sha2.h> +#include <apt-pkg/strutl.h> + +#include <algorithm> +#include <iostream> +#include <iterator> +#include <sstream> +#include <string> +#include <vector> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + /*}}}*/ + +using namespace std; + +// poor mans unordered_map::try_emplace for C++11 as it is a C++17 feature /*{{{*/ +template <typename Arg> +static void try_emplace(std::unordered_map<std::string, std::string> &fields, std::string &&name, Arg &&value) +{ + if (fields.find(name) == fields.end()) + fields.emplace(std::move(name), std::forward<Arg>(value)); +} + /*}}}*/ + +// AcqMethod::pkgAcqMethod - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* This constructs the initialization text */ +pkgAcqMethod::pkgAcqMethod(const char *Ver,unsigned long Flags) +{ + std::unordered_map<std::string, std::string> fields; + try_emplace(fields, "Version", Ver); + if ((Flags & SingleInstance) == SingleInstance) + try_emplace(fields, "Single-Instance", "true"); + + if ((Flags & Pipeline) == Pipeline) + try_emplace(fields, "Pipeline", "true"); + + if ((Flags & SendConfig) == SendConfig) + try_emplace(fields, "Send-Config", "true"); + + if ((Flags & LocalOnly) == LocalOnly) + try_emplace(fields, "Local-Only", "true"); + + if ((Flags & NeedsCleanup) == NeedsCleanup) + try_emplace(fields, "Needs-Cleanup", "true"); + + if ((Flags & Removable) == Removable) + try_emplace(fields, "Removable", "true"); + + if ((Flags & AuxRequests) == AuxRequests) + try_emplace(fields, "AuxRequests", "true"); + + SendMessage("100 Capabilities", std::move(fields)); + + SetNonBlock(STDIN_FILENO,true); + + Queue = 0; + QueueBack = 0; +} + /*}}}*/ +void pkgAcqMethod::SendMessage(std::string const &header, std::unordered_map<std::string, std::string> &&fields) /*{{{*/ +{ + auto CheckKey = [](std::string const &str) { + // Space, hyphen-minus, and alphanum are allowed for keys/headers. + return str.find_first_not_of(" -0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") == std::string::npos; + }; + + auto CheckValue = [](std::string const &str) { + return std::all_of(str.begin(), str.end(), [](unsigned char c) -> bool { + return c > 127 // unicode + || (c > 31 && c < 127) // printable chars + || c == '\n' || c == '\t'; // special whitespace + }); + }; + + auto Error = [this]() { + _error->Error("SECURITY: Message contains control characters, rejecting."); + _error->DumpErrors(); + SendMessage("400 URI Failure", {{"URI", "<UNKNOWN>"}, {"Message", "SECURITY: Message contains control characters, rejecting."}}); + abort(); + }; + + if (!CheckKey(header)) + return Error(); + + for (auto const &f : fields) + { + if (!CheckKey(f.first)) + return Error(); + if (!CheckValue(f.second)) + return Error(); + } + + std::cout << header << '\n'; + for (auto const &f : fields) + { + if (f.second.empty()) + continue; + std::cout << f.first << ": "; + auto const lines = VectorizeString(f.second, '\n'); + if (likely(lines.empty() == false)) + { + std::copy(lines.begin(), std::prev(lines.end()), std::ostream_iterator<std::string>(std::cout, "\n ")); + std::cout << *lines.rbegin(); + } + std::cout << '\n'; + } + std::cout << '\n' + << std::flush; +} + /*}}}*/ +// AcqMethod::Fail - A fetch has failed /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgAcqMethod::Fail(bool Transient) +{ + string Err = "Undetermined Error"; + if (_error->empty() == false) + { + Err.clear(); + while (_error->empty() == false) + { + std::string msg; + if (_error->PopMessage(msg)) + { + if (Err.empty() == false) + Err.append("\n"); + Err.append(msg); + } + } + } + Fail(Err, Transient); +} + /*}}}*/ +// AcqMethod::Fail - A fetch has failed /*{{{*/ +void pkgAcqMethod::Fail(string Err,bool Transient) +{ + // Strip out junk from the error messages + std::transform(Err.begin(), Err.end(), Err.begin(), [](char const c) { + if (c == '\r' || c == '\n') + return ' '; + return c; + }); + if (IP.empty() == false && _config->FindB("Acquire::Failure::ShowIP", true) == true) + Err.append(" ").append(IP); + + std::unordered_map<std::string, std::string> fields; + if (Queue != nullptr) + try_emplace(fields, "URI", Queue->Uri); + else + try_emplace(fields, "URI", "<UNKNOWN>"); + try_emplace(fields, "Message", Err); + + if(FailReason.empty() == false) + try_emplace(fields, "FailReason", FailReason); + if (UsedMirror.empty() == false) + try_emplace(fields, "UsedMirror", UsedMirror); + if (Transient == true) + try_emplace(fields, "Transient-Failure", "true"); + + SendMessage("400 URI Failure", std::move(fields)); + + if (Queue != nullptr) + Dequeue(); +} + /*}}}*/ +// AcqMethod::DropPrivsOrDie - Drop privileges or die /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgAcqMethod::DropPrivsOrDie() +{ + if (!DropPrivileges()) { + Fail(false); + exit(112); /* call the european emergency number */ + } +} + + /*}}}*/ +// AcqMethod::URIStart - Indicate a download is starting /*{{{*/ +void pkgAcqMethod::URIStart(FetchResult &Res) +{ + if (Queue == 0) + abort(); + + std::unordered_map<std::string, std::string> fields; + try_emplace(fields, "URI", Queue->Uri); + if (Res.Size != 0) + try_emplace(fields, "Size", std::to_string(Res.Size)); + if (Res.LastModified != 0) + try_emplace(fields, "Last-Modified", TimeRFC1123(Res.LastModified, true)); + if (Res.ResumePoint != 0) + try_emplace(fields, "Resume-Point", std::to_string(Res.ResumePoint)); + if (UsedMirror.empty() == false) + try_emplace(fields, "UsedMirror", UsedMirror); + + SendMessage("200 URI Start", std::move(fields)); +} + /*}}}*/ +// AcqMethod::URIDone - A URI is finished /*{{{*/ +static void printHashStringList(std::unordered_map<std::string, std::string> &fields, std::string const &Prefix, HashStringList const &list) +{ + for (auto const &hash : list) + { + // very old compatibility name for MD5Sum + if (hash.HashType() == "MD5Sum") + try_emplace(fields, Prefix + "MD5-Hash", hash.HashValue()); + try_emplace(fields, Prefix + hash.HashType() + "-Hash", hash.HashValue()); + } +} +void pkgAcqMethod::URIDone(FetchResult &Res, FetchResult *Alt) +{ + if (Queue == 0) + abort(); + + std::unordered_map<std::string, std::string> fields; + try_emplace(fields, "URI", Queue->Uri); + if (Res.Filename.empty() == false) + try_emplace(fields, "Filename", Res.Filename); + if (Res.Size != 0) + try_emplace(fields, "Size", std::to_string(Res.Size)); + if (Res.LastModified != 0) + try_emplace(fields, "Last-Modified", TimeRFC1123(Res.LastModified, true)); + printHashStringList(fields, "", Res.Hashes); + + if (UsedMirror.empty() == false) + try_emplace(fields, "UsedMirror", UsedMirror); + if (Res.GPGVOutput.empty() == false) + { + std::ostringstream os; + std::copy(Res.GPGVOutput.begin(), Res.GPGVOutput.end() - 1, std::ostream_iterator<std::string>(os, "\n")); + os << *Res.GPGVOutput.rbegin(); + try_emplace(fields, "GPGVOutput", os.str()); + } + if (Res.ResumePoint != 0) + try_emplace(fields, "Resume-Point", std::to_string(Res.ResumePoint)); + if (Res.IMSHit == true) + try_emplace(fields, "IMS-Hit", "true"); + + if (Alt != nullptr) + { + if (Alt->Filename.empty() == false) + try_emplace(fields, "Alt-Filename", Alt->Filename); + if (Alt->Size != 0) + try_emplace(fields, "Alt-Size", std::to_string(Alt->Size)); + if (Alt->LastModified != 0) + try_emplace(fields, "Alt-Last-Modified", TimeRFC1123(Alt->LastModified, true)); + if (Alt->IMSHit == true) + try_emplace(fields, "Alt-IMS-Hit", "true"); + printHashStringList(fields, "Alt-", Alt->Hashes); + } + + SendMessage("201 URI Done", std::move(fields)); + Dequeue(); +} + /*}}}*/ +// AcqMethod::MediaFail - Synchronous request for new media /*{{{*/ +// --------------------------------------------------------------------- +/* This sends a 403 Media Failure message to the APT and waits for it + to be ackd */ +bool pkgAcqMethod::MediaFail(string Required,string Drive) +{ + fprintf(stdout, "403 Media Failure\nMedia: %s\nDrive: %s\n", + Required.c_str(),Drive.c_str()); + std::cout << "\n" << std::flush; + + vector<string> MyMessages; + + /* Here we read messages until we find a 603, each non 603 message is + appended to the main message list for later processing */ + while (1) + { + if (WaitFd(STDIN_FILENO) == false) + return false; + + if (ReadMessages(STDIN_FILENO,MyMessages) == false) + return false; + + string Message = MyMessages.front(); + MyMessages.erase(MyMessages.begin()); + + // Fetch the message number + char *End; + int Number = strtol(Message.c_str(),&End,10); + if (End == Message.c_str()) + { + cerr << "Malformed message!" << endl; + exit(100); + } + + // Change ack + if (Number == 603) + { + while (MyMessages.empty() == false) + { + Messages.push_back(MyMessages.front()); + MyMessages.erase(MyMessages.begin()); + } + + return !StringToBool(LookupTag(Message,"Failed"),false); + } + + Messages.push_back(Message); + } +} + /*}}}*/ +// AcqMethod::Configuration - Handle the configuration message /*{{{*/ +// --------------------------------------------------------------------- +/* This parses each configuration entry and puts it into the _config + Configuration class. */ +bool pkgAcqMethod::Configuration(string Message) +{ + ::Configuration &Cnf = *_config; + + const char *I = Message.c_str(); + const char *MsgEnd = I + Message.length(); + + unsigned int Length = strlen("Config-Item"); + for (; I + Length < MsgEnd; I++) + { + // Not a config item + if (I[Length] != ':' || stringcasecmp(I,I+Length,"Config-Item") != 0) + continue; + + I += Length + 1; + + for (; I < MsgEnd && *I == ' '; I++); + const char *Equals = (const char*) memchr(I, '=', MsgEnd - I); + if (Equals == NULL) + return false; + const char *End = (const char*) memchr(Equals, '\n', MsgEnd - Equals); + if (End == NULL) + End = MsgEnd; + + Cnf.Set(DeQuoteString(string(I,Equals-I)), + DeQuoteString(string(Equals+1,End-Equals-1))); + I = End; + } + + return true; +} + /*}}}*/ +// AcqMethod::Run - Run the message engine /*{{{*/ +// --------------------------------------------------------------------- +/* Fetch any messages and execute them. In single mode it returns 1 if + there are no more available messages - any other result is a + fatal failure code! */ +int pkgAcqMethod::Run(bool Single) +{ + while (1) + { + // Block if the message queue is empty + if (Messages.empty() == true) + { + if (Single == false) + if (WaitFd(STDIN_FILENO) == false) + break; + if (ReadMessages(STDIN_FILENO,Messages) == false) + break; + } + + // Single mode exits if the message queue is empty + if (Single == true && Messages.empty() == true) + return -1; + + string Message = Messages.front(); + Messages.erase(Messages.begin()); + + // Fetch the message number + char *End; + int Number = strtol(Message.c_str(),&End,10); + if (End == Message.c_str()) + { + cerr << "Malformed message!" << endl; + return 100; + } + + switch (Number) + { + case 601: + if (Configuration(Message) == false) + return 100; + break; + + case 600: + { + FetchItem *Tmp = new FetchItem; + + Tmp->Uri = LookupTag(Message,"URI"); + Tmp->Proxy(LookupTag(Message, "Proxy")); + Tmp->DestFile = LookupTag(Message,"FileName"); + if (RFC1123StrToTime(LookupTag(Message,"Last-Modified").c_str(),Tmp->LastModified) == false) + Tmp->LastModified = 0; + Tmp->IndexFile = StringToBool(LookupTag(Message,"Index-File"),false); + Tmp->FailIgnore = StringToBool(LookupTag(Message,"Fail-Ignore"),false); + Tmp->ExpectedHashes = HashStringList(); + for (char const * const * t = HashString::SupportedHashes(); *t != NULL; ++t) + { + std::string tag = "Expected-"; + tag.append(*t); + std::string const hash = LookupTag(Message, tag.c_str()); + if (hash.empty() == false) + Tmp->ExpectedHashes.push_back(HashString(*t, hash)); + } + char *End; + if (Tmp->ExpectedHashes.FileSize() > 0) + Tmp->MaximumSize = Tmp->ExpectedHashes.FileSize(); + else + Tmp->MaximumSize = strtoll(LookupTag(Message, "Maximum-Size", "0").c_str(), &End, 10); + Tmp->Next = 0; + + // Append it to the list + FetchItem **I = &Queue; + for (; *I != 0; I = &(*I)->Next); + *I = Tmp; + if (QueueBack == 0) + QueueBack = Tmp; + + // Notify that this item is to be fetched. + if (URIAcquire(Message, Tmp) == false) + Fail(); + + break; + } + } + } + + Exit(); + return 0; +} + /*}}}*/ +// AcqMethod::PrintStatus - privately really send a log/status message /*{{{*/ +void pkgAcqMethod::PrintStatus(char const * const header, const char* Format, + va_list &args) const +{ + string CurrentURI = "<UNKNOWN>"; + if (Queue != 0) + CurrentURI = Queue->Uri; + if (UsedMirror.empty() == true) + fprintf(stdout, "%s\nURI: %s\nMessage: ", + header, CurrentURI.c_str()); + else + fprintf(stdout, "%s\nURI: %s\nUsedMirror: %s\nMessage: ", + header, CurrentURI.c_str(), UsedMirror.c_str()); + vfprintf(stdout,Format,args); + std::cout << "\n\n" << std::flush; +} + /*}}}*/ +// AcqMethod::Log - Send a log message /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgAcqMethod::Log(const char *Format,...) +{ + va_list args; + va_start(args,Format); + PrintStatus("101 Log", Format, args); + va_end(args); +} + /*}}}*/ +// AcqMethod::Status - Send a status message /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgAcqMethod::Status(const char *Format,...) +{ + va_list args; + va_start(args,Format); + PrintStatus("102 Status", Format, args); + va_end(args); +} + /*}}}*/ +// AcqMethod::Redirect - Send a redirect message /*{{{*/ +// --------------------------------------------------------------------- +/* This method sends the redirect message and dequeues the item as + * the worker will enqueue again later on to the right queue */ +void pkgAcqMethod::Redirect(const string &NewURI) +{ + if (NewURI.find_first_not_of(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~") != std::string::npos) + { + _error->Error("SECURITY: URL redirect target contains control characters, rejecting."); + Fail(); + return; + } + std::unordered_map<std::string, std::string> fields; + try_emplace(fields, "URI", Queue->Uri); + try_emplace(fields, "New-URI", NewURI); + SendMessage("103 Redirect", std::move(fields)); + Dequeue(); +} + /*}}}*/ +// AcqMethod::FetchResult::FetchResult - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgAcqMethod::FetchResult::FetchResult() : LastModified(0), + IMSHit(false), Size(0), ResumePoint(0), d(NULL) +{ +} + /*}}}*/ +// AcqMethod::FetchResult::TakeHashes - Load hashes /*{{{*/ +// --------------------------------------------------------------------- +/* This hides the number of hashes we are supporting from the caller. + It just deals with the hash class. */ +void pkgAcqMethod::FetchResult::TakeHashes(class Hashes &Hash) +{ + Hashes = Hash.GetHashStringList(); +} + /*}}}*/ +void pkgAcqMethod::Dequeue() { /*{{{*/ + FetchItem const * const Tmp = Queue; + Queue = Queue->Next; + if (Tmp == QueueBack) + QueueBack = Queue; + delete Tmp; +} + /*}}}*/ +pkgAcqMethod::~pkgAcqMethod() {} + +struct pkgAcqMethod::FetchItem::Private +{ + std::string Proxy; +}; + +pkgAcqMethod::FetchItem::FetchItem() : Next(nullptr), DestFileFd(-1), LastModified(0), IndexFile(false), + FailIgnore(false), MaximumSize(0), d(new Private) +{} + +std::string pkgAcqMethod::FetchItem::Proxy() +{ + return d->Proxy; +} + +void pkgAcqMethod::FetchItem::Proxy(std::string const &Proxy) +{ + d->Proxy = Proxy; +} + +pkgAcqMethod::FetchItem::~FetchItem() { delete d; } + +pkgAcqMethod::FetchResult::~FetchResult() {} diff --git a/apt-pkg/acquire-method.h b/apt-pkg/acquire-method.h new file mode 100644 index 0000000..664b95c --- /dev/null +++ b/apt-pkg/acquire-method.h @@ -0,0 +1,140 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Acquire Method - Method helper class + functions + + These functions are designed to be used within the method task to + ease communication with APT. + + ##################################################################### */ + /*}}}*/ + +/** \addtogroup acquire + * @{ + * + * \file acquire-method.h + */ + +#ifndef PKGLIB_ACQUIRE_METHOD_H +#define PKGLIB_ACQUIRE_METHOD_H + +#include <apt-pkg/hashes.h> +#include <apt-pkg/macros.h> + +#include <stdarg.h> +#include <time.h> + +#include <string> +#include <unordered_map> +#include <vector> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/configuration.h> +#include <apt-pkg/strutl.h> +#endif + +class pkgAcqMethod +{ + protected: + + struct FetchItem + { + FetchItem *Next; + + std::string Uri; + std::string DestFile; + int DestFileFd; + time_t LastModified; + bool IndexFile; + bool FailIgnore; + HashStringList ExpectedHashes; + // a maximum size we will download, this can be the exact filesize + // for when we know it or a arbitrary limit when we don't know the + // filesize (like a InRelease file) + unsigned long long MaximumSize; + + FetchItem(); + virtual ~FetchItem(); + std::string Proxy(); // For internal use only. + void Proxy(std::string const &Proxy) APT_HIDDEN; + + private: + struct Private; + Private *const d; + }; + + struct FetchResult + { + HashStringList Hashes; + std::vector<std::string> GPGVOutput; + time_t LastModified; + bool IMSHit; + std::string Filename; + unsigned long long Size; + unsigned long long ResumePoint; + + void TakeHashes(class Hashes &Hash); + FetchResult(); + virtual ~FetchResult(); + private: + void * const d; + }; + + // State + std::vector<std::string> Messages; + FetchItem *Queue; + FetchItem *QueueBack; + std::string FailReason; + std::string UsedMirror; + std::string IP; + + // Handlers for messages + virtual bool Configuration(std::string Message); + virtual bool Fetch(FetchItem * /*Item*/) {return true;}; + virtual bool URIAcquire(std::string const &/*Message*/, FetchItem *Itm) { return Fetch(Itm); }; + + // Outgoing messages + void Fail(bool Transient = false); + inline void Fail(const char *Why, bool Transient = false) {Fail(std::string(Why),Transient);}; + virtual void Fail(std::string Why, bool Transient = false); + virtual void URIStart(FetchResult &Res); + virtual void URIDone(FetchResult &Res,FetchResult *Alt = 0); + void SendMessage(std::string const &header, std::unordered_map<std::string, std::string> &&fields); + + bool MediaFail(std::string Required,std::string Drive); + virtual void Exit() {}; + + void PrintStatus(char const * const header, const char* Format, va_list &args) const; + + public: + enum CnfFlags + { + SingleInstance = (1 << 0), + Pipeline = (1 << 1), + SendConfig = (1 << 2), + LocalOnly = (1 << 3), + NeedsCleanup = (1 << 4), + Removable = (1 << 5), + AuxRequests = (1 << 6) + }; + + void Log(const char *Format,...); + void Status(const char *Format,...); + + void Redirect(const std::string &NewURI); + + int Run(bool Single = false); + inline void SetFailReason(std::string Msg) {FailReason = Msg;}; + inline void SetIP(std::string aIP) {IP = aIP;}; + + pkgAcqMethod(const char *Ver,unsigned long Flags = 0); + virtual ~pkgAcqMethod(); + void DropPrivsOrDie(); + private: + APT_HIDDEN void Dequeue(); +}; + +/** @} */ + +#endif diff --git a/apt-pkg/acquire-worker.cc b/apt-pkg/acquire-worker.cc new file mode 100644 index 0000000..b361861 --- /dev/null +++ b/apt-pkg/acquire-worker.cc @@ -0,0 +1,964 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Acquire Worker + + The worker process can startup either as a Configuration prober + or as a queue runner. As a configuration prober it only reads the + configuration message and + + ##################################################################### */ + /*}}}*/ +// 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/fileutl.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/proxy.h> +#include <apt-pkg/strutl.h> + +#include <algorithm> +#include <iostream> +#include <string> +#include <vector> + +#include <sstream> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ + +using namespace std; + +// Worker::Worker - Constructor for Queue startup /*{{{*/ +pkgAcquire::Worker::Worker(Queue *Q, MethodConfig *Cnf, pkgAcquireStatus *log) : + d(NULL), OwnerQ(Q), Log(log), Config(Cnf), Access(Cnf->Access), + CurrentItem(nullptr), CurrentSize(0), TotalSize(0) +{ + Construct(); +} + /*}}}*/ +// Worker::Worker - Constructor for method config startup /*{{{*/ +pkgAcquire::Worker::Worker(MethodConfig *Cnf) : Worker(nullptr, Cnf, nullptr) +{ +} + /*}}}*/ +// Worker::Construct - Constructor helper /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgAcquire::Worker::Construct() +{ + NextQueue = 0; + NextAcquire = 0; + Process = -1; + InFd = -1; + OutFd = -1; + OutReady = false; + InReady = false; + Debug = _config->FindB("Debug::pkgAcquire::Worker",false); +} + /*}}}*/ +// Worker::~Worker - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgAcquire::Worker::~Worker() +{ + close(InFd); + close(OutFd); + + if (Process > 0) + { + /* Closing of stdin is the signal to exit and die when the process + indicates it needs cleanup */ + if (Config->NeedsCleanup == false) + kill(Process,SIGINT); + ExecWait(Process,Access.c_str(),true); + } +} + /*}}}*/ +// Worker::Start - Start the worker process /*{{{*/ +// --------------------------------------------------------------------- +/* This forks the method and inits the communication channel */ +bool pkgAcquire::Worker::Start() +{ + // Get the method path + constexpr char const * const methodsDir = "Dir::Bin::Methods"; + std::string const confItem = std::string(methodsDir) + "::" + Access; + std::string Method; + if (_config->Exists(confItem)) + Method = _config->FindFile(confItem.c_str()); + else if (Access == "ftp" || Access == "rsh" || Access == "ssh") + return _error->Error(_("The method '%s' is unsupported and disabled by default. Consider switching to http(s). Set Dir::Bin::Methods::%s to \"%s\" to enable it again."), Access.c_str(), Access.c_str(), Access.c_str()); + else + Method = _config->FindDir(methodsDir) + Access; + if (FileExists(Method) == false) + { + if (flNotDir(Method) == "false") + { + _error->Error(_("The method '%s' is explicitly disabled via configuration."), Access.c_str()); + if (Access == "http" || Access == "https") + _error->Notice(_("If you meant to use Tor remember to use %s instead of %s."), ("tor+" + Access).c_str(), Access.c_str()); + return false; + } + _error->Error(_("The method driver %s could not be found."),Method.c_str()); + std::string const A(Access.cbegin(), std::find(Access.cbegin(), Access.cend(), '+')); + std::string pkg; + strprintf(pkg, "apt-transport-%s", A.c_str()); + _error->Notice(_("Is the package %s installed?"), pkg.c_str()); + return false; + } + std::string const Calling = _config->FindDir(methodsDir) + Access; + + if (Debug == true) + { + std::clog << "Starting method '" << Calling << "'"; + if (Calling != Method) + std::clog << " ( via " << Method << " )"; + std::clog << endl; + } + + // Create the pipes + int Pipes[4] = {-1,-1,-1,-1}; + if (pipe(Pipes) != 0 || pipe(Pipes+2) != 0) + { + _error->Errno("pipe","Failed to create IPC pipe to subprocess"); + for (int I = 0; I != 4; I++) + close(Pipes[I]); + return false; + } + for (int I = 0; I != 4; I++) + SetCloseExec(Pipes[I],true); + + // Fork off the process + Process = ExecFork(); + if (Process == 0) + { + // Setup the FDs + dup2(Pipes[1],STDOUT_FILENO); + dup2(Pipes[2],STDIN_FILENO); + SetCloseExec(STDOUT_FILENO,false); + SetCloseExec(STDIN_FILENO,false); + SetCloseExec(STDERR_FILENO,false); + + const char * const Args[] = { Calling.c_str(), nullptr }; + execv(Method.c_str() ,const_cast<char **>(Args)); + std::cerr << "Failed to exec method " << Calling << " ( via " << Method << ")" << endl; + _exit(100); + } + + // Fix up our FDs + InFd = Pipes[0]; + OutFd = Pipes[3]; + SetNonBlock(Pipes[0],true); + SetNonBlock(Pipes[3],true); + close(Pipes[1]); + close(Pipes[2]); + OutReady = false; + InReady = true; + + // Read the configuration data + if (WaitFd(InFd) == false || + ReadMessages() == false) + return _error->Error(_("Method %s did not start correctly"),Method.c_str()); + + RunMessages(); + if (OwnerQ != 0) + SendConfiguration(); + + return true; +} + /*}}}*/ +// Worker::ReadMessages - Read all pending messages into the list /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgAcquire::Worker::ReadMessages() +{ + if (::ReadMessages(InFd,MessageQueue) == false) + return MethodFailure(); + return true; +} + /*}}}*/ +// Worker::RunMessage - Empty the message queue /*{{{*/ +// --------------------------------------------------------------------- +/* This takes the messages from the message queue and runs them through + the parsers in order. */ +enum class APT_HIDDEN MessageType +{ + CAPABILITIES = 100, + LOG = 101, + STATUS = 102, + REDIRECT = 103, + WARNING = 104, + URI_START = 200, + URI_DONE = 201, + AUX_REQUEST = 351, + URI_FAILURE = 400, + GENERAL_FAILURE = 401, + MEDIA_CHANGE = 403 +}; +static bool isDoomedItem(pkgAcquire::Item const * const Itm) +{ + auto const TransItm = dynamic_cast<pkgAcqTransactionItem const * const>(Itm); + if (TransItm == nullptr) + return false; + return TransItm->TransactionManager->State != pkgAcqTransactionItem::TransactionStarted; +} +static HashStringList GetHashesFromMessage(std::string const &Prefix, std::string const &Message) +{ + HashStringList hsl; + for (char const *const *type = HashString::SupportedHashes(); *type != NULL; ++type) + { + std::string const tagname = Prefix + *type + "-Hash"; + std::string const hashsum = LookupTag(Message, tagname.c_str()); + if (hashsum.empty() == false) + hsl.push_back(HashString(*type, hashsum)); + } + return hsl; +} +static void APT_NONNULL(3) ChangeSiteIsMirrorChange(std::string const &NewURI, pkgAcquire::ItemDesc &desc, pkgAcquire::Item *const Owner) +{ + if (URI::SiteOnly(NewURI) == URI::SiteOnly(desc.URI)) + return; + + auto const firstSpace = desc.Description.find(" "); + if (firstSpace != std::string::npos) + { + std::string const OldSite = desc.Description.substr(0, firstSpace); + if (likely(APT::String::Startswith(desc.URI, OldSite))) + { + std::string const OldExtra = desc.URI.substr(OldSite.length() + 1); + if (likely(APT::String::Endswith(NewURI, OldExtra))) + { + std::string const NewSite = NewURI.substr(0, NewURI.length() - OldExtra.length()); + Owner->UsedMirror = URI::ArchiveOnly(NewSite); + desc.Description.replace(0, firstSpace, Owner->UsedMirror); + } + } + } +} +bool pkgAcquire::Worker::RunMessages() +{ + while (MessageQueue.empty() == false) + { + string Message = MessageQueue.front(); + MessageQueue.erase(MessageQueue.begin()); + + if (Debug == true) + clog << " <- " << Access << ':' << QuoteString(Message,"\n") << endl; + + // Fetch the message number + char *End; + MessageType const Number = static_cast<MessageType>(strtoul(Message.c_str(),&End,10)); + if (End == Message.c_str()) + return _error->Error("Invalid message from method %s: %s",Access.c_str(),Message.c_str()); + + string URI = LookupTag(Message,"URI"); + pkgAcquire::Queue::QItem *Itm = NULL; + if (URI.empty() == false) + Itm = OwnerQ->FindItem(URI,this); + + if (Itm != NULL) + { + // update used mirror + string UsedMirror = LookupTag(Message,"UsedMirror", ""); + if (UsedMirror.empty() == false) + { + for (pkgAcquire::Queue::QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O) + (*O)->UsedMirror = UsedMirror; + + if (Itm->Description.find(" ") != string::npos) + Itm->Description.replace(0, Itm->Description.find(" "), UsedMirror); + } + } + + // Determine the message number and dispatch + switch (Number) + { + case MessageType::CAPABILITIES: + if (Capabilities(Message) == false) + return _error->Error("Unable to process Capabilities message from %s",Access.c_str()); + break; + + case MessageType::LOG: + if (Debug == true) + clog << " <- (log) " << LookupTag(Message,"Message") << endl; + break; + + case MessageType::STATUS: + Status = LookupTag(Message,"Message"); + break; + + case MessageType::REDIRECT: + { + if (Itm == nullptr) + { + _error->Error("Method gave invalid 103 Redirect message"); + break; + } + + std::string const NewURI = LookupTag(Message,"New-URI",URI.c_str()); + Itm->URI = NewURI; + auto const AltUris = VectorizeString(LookupTag(Message, "Alternate-URIs"), '\n'); + + ItemDone(); + + // Change the status so that it can be dequeued + for (auto const &O: Itm->Owners) + O->Status = pkgAcquire::Item::StatIdle; + // Mark the item as done (taking care of all queues) + // and then put it in the main queue again + std::vector<Item*> const ItmOwners = Itm->Owners; + OwnerQ->ItemDone(Itm); + Itm = nullptr; + for (auto const &Owner: ItmOwners) + { + for (auto alt = AltUris.crbegin(); alt != AltUris.crend(); ++alt) + Owner->PushAlternativeURI(std::string(*alt), {}, false); + + pkgAcquire::ItemDesc &desc = Owner->GetItemDesc(); + // for a simplified retry a method might redirect without URI change + // see also IsRedirectionLoop implementation + if (desc.URI != NewURI) + { + auto newuri = NewURI; + if (Owner->IsGoodAlternativeURI(newuri) == false && Owner->PopAlternativeURI(newuri) == false) + newuri.clear(); + if (newuri.empty() || Owner->IsRedirectionLoop(newuri)) + { + std::string msg = Message; + msg.append("\nFailReason: RedirectionLoop"); + Owner->Failed(msg, Config); + if (Log != nullptr) + Log->Fail(Owner->GetItemDesc()); + continue; + } + + if (Log != nullptr) + Log->Done(desc); + + ChangeSiteIsMirrorChange(NewURI, desc, Owner); + desc.URI = NewURI; + } + if (isDoomedItem(Owner) == false) + OwnerQ->Owner->Enqueue(desc); + } + break; + } + + case MessageType::WARNING: + _error->Warning("%s: %s", Itm ? Itm->Owner ? Itm->Owner->DescURI().c_str() : Access.c_str() : Access.c_str(), LookupTag(Message, "Message").c_str()); + break; + + case MessageType::URI_START: + { + if (Itm == nullptr) + { + _error->Error("Method gave invalid 200 URI Start message"); + break; + } + + CurrentItem = Itm; + CurrentSize = 0; + TotalSize = strtoull(LookupTag(Message,"Size","0").c_str(), NULL, 10); + ResumePoint = strtoull(LookupTag(Message,"Resume-Point","0").c_str(), NULL, 10); + for (auto const Owner: Itm->Owners) + { + Owner->Start(Message, TotalSize); + // Display update before completion + if (Log != nullptr) + { + if (Log->MorePulses == true) + Log->Pulse(Owner->GetOwner()); + Log->Fetch(Owner->GetItemDesc()); + } + } + + break; + } + + case MessageType::URI_DONE: + { + if (Itm == nullptr) + { + _error->Error("Method gave invalid 201 URI Done message"); + break; + } + + PrepareFiles("201::URIDone", Itm); + + // Display update before completion + if (Log != 0 && Log->MorePulses == true) + for (pkgAcquire::Queue::QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O) + Log->Pulse((*O)->GetOwner()); + + HashStringList ReceivedHashes; + { + std::string const givenfilename = LookupTag(Message, "Filename"); + std::string const filename = givenfilename.empty() ? Itm->Owner->DestFile : givenfilename; + // see if we got hashes to verify + ReceivedHashes = GetHashesFromMessage("", Message); + // not all methods always sent Hashes our way + if (ReceivedHashes.usable() == false) + { + HashStringList const ExpectedHashes = Itm->GetExpectedHashes(); + if (ExpectedHashes.usable() == true && RealFileExists(filename)) + { + Hashes calc(ExpectedHashes); + FileFd file(filename, FileFd::ReadOnly, FileFd::None); + calc.AddFD(file); + ReceivedHashes = calc.GetHashStringList(); + } + } + + // only local files can refer other filenames and counting them as fetched would be unfair + if (Log != NULL && Itm->Owner->Complete == false && Itm->Owner->Local == false && givenfilename == filename) + Log->Fetched(ReceivedHashes.FileSize(),atoi(LookupTag(Message,"Resume-Point","0").c_str())); + } + + std::vector<Item*> const ItmOwners = Itm->Owners; + OwnerQ->ItemDone(Itm); + Itm = NULL; + + bool const isIMSHit = StringToBool(LookupTag(Message,"IMS-Hit"),false) || + StringToBool(LookupTag(Message,"Alt-IMS-Hit"),false); + auto const forcedHash = _config->Find("Acquire::ForceHash"); + for (auto const Owner: ItmOwners) + { + HashStringList const ExpectedHashes = Owner->GetExpectedHashes(); + if(_config->FindB("Debug::pkgAcquire::Auth", false) == true) + { + std::clog << "201 URI Done: " << Owner->DescURI() << endl + << "ReceivedHash:" << endl; + for (HashStringList::const_iterator hs = ReceivedHashes.begin(); hs != ReceivedHashes.end(); ++hs) + std::clog << "\t- " << hs->toStr() << std::endl; + std::clog << "ExpectedHash:" << endl; + for (HashStringList::const_iterator hs = ExpectedHashes.begin(); hs != ExpectedHashes.end(); ++hs) + std::clog << "\t- " << hs->toStr() << std::endl; + std::clog << endl; + } + + // decide if what we got is what we expected + bool consideredOkay = false; + if ((forcedHash.empty() && ExpectedHashes.empty() == false) || + (forcedHash.empty() == false && ExpectedHashes.usable())) + { + if (ReceivedHashes.empty()) + { + /* IMS-Hits can't be checked here as we will have uncompressed file, + but the hashes for the compressed file. What we have was good through + so all we have to ensure later is that we are not stalled. */ + consideredOkay = isIMSHit; + } + else if (ReceivedHashes == ExpectedHashes) + consideredOkay = true; + else + consideredOkay = false; + + } + else + consideredOkay = !Owner->HashesRequired(); + + if (consideredOkay == true) + consideredOkay = Owner->VerifyDone(Message, Config); + else // hashsum mismatch + Owner->Status = pkgAcquire::Item::StatAuthError; + + + if (consideredOkay == true) + { + if (isDoomedItem(Owner) == false) + Owner->Done(Message, ReceivedHashes, Config); + if (Log != nullptr) + { + if (isIMSHit) + Log->IMSHit(Owner->GetItemDesc()); + else + Log->Done(Owner->GetItemDesc()); + } + } + else + { + auto SavedDesc = Owner->GetItemDesc(); + if (isDoomedItem(Owner) == false) + { + if (Message.find("\nFailReason:") == std::string::npos) + { + if (ReceivedHashes != ExpectedHashes) + Message.append("\nFailReason: HashSumMismatch"); + else + Message.append("\nFailReason: WeakHashSums"); + } + Owner->Failed(Message,Config); + } + if (Log != nullptr) + Log->Fail(SavedDesc); + } + } + ItemDone(); + break; + } + + case MessageType::AUX_REQUEST: + { + if (Itm == nullptr) + { + _error->Error("Method gave invalid Aux Request message"); + break; + } + else if (Config->GetAuxRequests() == false) + { + std::vector<Item *> const ItmOwners = Itm->Owners; + Message.append("\nMessage: Method tried to make an Aux Request while not being allowed to do them"); + OwnerQ->ItemDone(Itm); + Itm = nullptr; + HandleFailure(ItmOwners, Config, Log, Message, false, false); + ItemDone(); + + std::string Msg = "600 URI Acquire\n"; + Msg.reserve(200); + Msg += "URI: " + LookupTag(Message, "Aux-URI", ""); + Msg += "\nFilename: /nonexistent/auxrequest.blocked"; + Msg += "\n\n"; + if (Debug == true) + clog << " -> " << Access << ':' << QuoteString(Msg, "\n") << endl; + OutQueue += Msg; + OutReady = true; + break; + } + + auto maxsizestr = LookupTag(Message, "MaximumSize", ""); + unsigned long long const MaxSize = maxsizestr.empty() ? 0 : strtoull(maxsizestr.c_str(), nullptr, 10); + new pkgAcqAuxFile(Itm->Owner, this, LookupTag(Message, "Aux-ShortDesc", ""), + LookupTag(Message, "Aux-Description", ""), LookupTag(Message, "Aux-URI", ""), + GetHashesFromMessage("Aux-", Message), MaxSize); + break; + } + + case MessageType::URI_FAILURE: + { + if (Itm == nullptr) + { + std::string const msg = LookupTag(Message,"Message"); + _error->Error("Method gave invalid 400 URI Failure message: %s", msg.c_str()); + break; + } + + PrepareFiles("400::URIFailure", Itm); + + // Display update before completion + if (Log != nullptr && Log->MorePulses == true) + for (pkgAcquire::Queue::QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O) + Log->Pulse((*O)->GetOwner()); + + std::vector<Item*> const ItmOwners = Itm->Owners; + OwnerQ->ItemDone(Itm); + Itm = nullptr; + + bool errTransient = false, errAuthErr = false; + if (StringToBool(LookupTag(Message, "Transient-Failure"), false) == true) + errTransient = true; + else + { + std::string const failReason = LookupTag(Message, "FailReason"); + { + auto const reasons = { "Timeout", "ConnectionRefused", + "ConnectionTimedOut", "ResolveFailure", "TmpResolveFailure" }; + errTransient = std::find(std::begin(reasons), std::end(reasons), failReason) != std::end(reasons); + } + if (errTransient == false) + { + auto const reasons = { "HashSumMismatch", "WeakHashSums", "MaximumSizeExceeded" }; + errAuthErr = std::find(std::begin(reasons), std::end(reasons), failReason) != std::end(reasons); + } + } + HandleFailure(ItmOwners, Config, Log, Message, errTransient, errAuthErr); + ItemDone(); + + break; + } + + case MessageType::GENERAL_FAILURE: + _error->Error("Method %s General failure: %s",Access.c_str(),LookupTag(Message,"Message").c_str()); + break; + + case MessageType::MEDIA_CHANGE: + MediaChange(Message); + break; + } + } + return true; +} + /*}}}*/ +void pkgAcquire::Worker::HandleFailure(std::vector<pkgAcquire::Item *> const &ItmOwners, /*{{{*/ + pkgAcquire::MethodConfig *const Config, pkgAcquireStatus *const Log, + std::string const &Message, bool const errTransient, bool const errAuthErr) +{ + for (auto const Owner : ItmOwners) + { + std::string NewURI; + if (errTransient == true && Config->LocalOnly == false && Owner->ModifyRetries() != 0) + { + --Owner->ModifyRetries(); + Owner->FailMessage(Message); + auto SavedDesc = Owner->GetItemDesc(); + if (Log != nullptr) + Log->Fail(SavedDesc); + if (isDoomedItem(Owner) == false) + OwnerQ->Owner->Enqueue(SavedDesc); + } + else + { + if (errAuthErr) + Owner->RemoveAlternativeSite(URI::SiteOnly(Owner->GetItemDesc().URI)); + if (Owner->PopAlternativeURI(NewURI)) + { + Owner->FailMessage(Message); + auto &desc = Owner->GetItemDesc(); + if (Log != nullptr) + Log->Fail(desc); + ChangeSiteIsMirrorChange(NewURI, desc, Owner); + desc.URI = NewURI; + if (isDoomedItem(Owner) == false) + OwnerQ->Owner->Enqueue(desc); + } + else + { + if (errAuthErr && Owner->GetExpectedHashes().empty() == false) + Owner->Status = pkgAcquire::Item::StatAuthError; + else if (errTransient) + Owner->Status = pkgAcquire::Item::StatTransientNetworkError; + auto SavedDesc = Owner->GetItemDesc(); + if (isDoomedItem(Owner) == false) + Owner->Failed(Message, Config); + if (Log != nullptr) + Log->Fail(SavedDesc); + } + } + } +} + /*}}}*/ +// Worker::Capabilities - 100 Capabilities handler /*{{{*/ +// --------------------------------------------------------------------- +/* This parses the capabilities message and dumps it into the configuration + structure. */ +bool pkgAcquire::Worker::Capabilities(string Message) +{ + if (Config == 0) + return true; + + Config->Version = LookupTag(Message,"Version"); + Config->SingleInstance = StringToBool(LookupTag(Message,"Single-Instance"),false); + Config->Pipeline = StringToBool(LookupTag(Message,"Pipeline"),false); + Config->SendConfig = StringToBool(LookupTag(Message,"Send-Config"),false); + Config->LocalOnly = StringToBool(LookupTag(Message,"Local-Only"),false); + Config->NeedsCleanup = StringToBool(LookupTag(Message,"Needs-Cleanup"),false); + Config->Removable = StringToBool(LookupTag(Message,"Removable"),false); + Config->SetAuxRequests(StringToBool(LookupTag(Message, "AuxRequests"), false)); + + // Some debug text + if (Debug == true) + { + clog << "Configured access method " << Config->Access << endl; + clog << "Version:" << Config->Version << " SingleInstance:" << Config->SingleInstance << " Pipeline:" << Config->Pipeline << " SendConfig:" << Config->SendConfig << " LocalOnly: " << Config->LocalOnly << " NeedsCleanup: " << Config->NeedsCleanup << " Removable: " << Config->Removable << " AuxRequests: " << Config->GetAuxRequests() << endl; + } + + return true; +} + /*}}}*/ +// Worker::MediaChange - Request a media change /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgAcquire::Worker::MediaChange(string Message) +{ + int status_fd = _config->FindI("APT::Status-Fd",-1); + if(status_fd > 0) + { + string Media = LookupTag(Message,"Media"); + string Drive = LookupTag(Message,"Drive"); + ostringstream msg,status; + ioprintf(msg,_("Please insert the disc labeled: " + "'%s' " + "in the drive '%s' and press [Enter]."), + Media.c_str(),Drive.c_str()); + status << "media-change: " // message + << Media << ":" // media + << Drive << ":" // drive + << msg.str() // l10n message + << endl; + + std::string const dlstatus = status.str(); + FileFd::Write(status_fd, dlstatus.c_str(), dlstatus.size()); + } + + if (Log == 0 || Log->MediaChange(LookupTag(Message,"Media"), + LookupTag(Message,"Drive")) == false) + { + char S[300]; + snprintf(S,sizeof(S),"603 Media Changed\nFailed: true\n\n"); + if (Debug == true) + clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl; + OutQueue += S; + OutReady = true; + return true; + } + + char S[300]; + snprintf(S,sizeof(S),"603 Media Changed\n\n"); + if (Debug == true) + clog << " -> " << Access << ':' << QuoteString(S,"\n") << endl; + OutQueue += S; + OutReady = true; + return true; +} + /*}}}*/ +// Worker::SendConfiguration - Send the config to the method /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgAcquire::Worker::SendConfiguration() +{ + if (Config->SendConfig == false) + return true; + + if (OutFd == -1) + return false; + + /* Write out all of the configuration directives by walking the + configuration tree */ + std::ostringstream Message; + Message << "601 Configuration\n"; + _config->Dump(Message, NULL, "Config-Item: %F=%V\n", false); + Message << '\n'; + + if (Debug == true) + clog << " -> " << Access << ':' << QuoteString(Message.str(),"\n") << endl; + OutQueue += Message.str(); + OutReady = true; + + return true; +} + /*}}}*/ +// Worker::QueueItem - Add an item to the outbound queue /*{{{*/ +// --------------------------------------------------------------------- +/* Send a URI Acquire message to the method */ +bool pkgAcquire::Worker::QueueItem(pkgAcquire::Queue::QItem *Item) +{ + if (OutFd == -1) + return false; + + if (isDoomedItem(Item->Owner)) + return true; + + Item->SyncDestinationFiles(); + + string Message = "600 URI Acquire\n"; + Message.reserve(300); + Message += "URI: " + Item->URI; + Message += "\nFilename: " + Item->Owner->DestFile; + + URI URL = Item->URI; + // FIXME: We should not hard code proxy protocols here. + if (URL.Access == "http" || URL.Access == "https") + { + AutoDetectProxy(URL); + if (_config->Exists("Acquire::" + URL.Access + "::proxy::" + URL.Host)) + { + Message += "\nProxy: " + _config->Find("Acquire::" + URL.Access + "::proxy::" + URL.Host); + } + } + + HashStringList const hsl = Item->GetExpectedHashes(); + for (HashStringList::const_iterator hs = hsl.begin(); hs != hsl.end(); ++hs) + Message += "\nExpected-" + hs->HashType() + ": " + hs->HashValue(); + + Message += Item->Custom600Headers(); + + if (hsl.FileSize() == 0 && Message.find("\nMaximum-Size: ") == std::string::npos) + { + unsigned long long FileSize = Item->GetMaximumSize(); + if(FileSize > 0) + { + string MaximumSize; + strprintf(MaximumSize, "%llu", FileSize); + Message += "\nMaximum-Size: " + MaximumSize; + } + } + + Message += "\n\n"; + + if (RealFileExists(Item->Owner->DestFile)) + { + std::string const SandboxUser = _config->Find("APT::Sandbox::User"); + ChangeOwnerAndPermissionOfFile("Item::QueueURI", Item->Owner->DestFile.c_str(), + SandboxUser.c_str(), ROOT_GROUP, 0600); + } + + if (Debug == true) + clog << " -> " << Access << ':' << QuoteString(Message,"\n") << endl; + OutQueue += Message; + OutReady = true; + + return true; +} + /*}}}*/ +// Worker::ReplyAux - reply to an aux request from this worker /*{{{*/ +bool pkgAcquire::Worker::ReplyAux(pkgAcquire::ItemDesc const &Item) +{ + if (OutFd == -1) + return false; + + if (isDoomedItem(Item.Owner)) + return true; + + string Message = "600 URI Acquire\n"; + Message.reserve(200); + Message += "URI: " + Item.URI; + if (RealFileExists(Item.Owner->DestFile)) + { + if (Item.Owner->Status == pkgAcquire::Item::StatDone) + { + std::string const SandboxUser = _config->Find("APT::Sandbox::User"); + ChangeOwnerAndPermissionOfFile("Worker::ReplyAux", Item.Owner->DestFile.c_str(), + SandboxUser.c_str(), ROOT_GROUP, 0600); + Message += "\nFilename: " + Item.Owner->DestFile; + } + else + { + // we end up here in case we would need root-rights to delete a file, + // but we run the command as non-root… (yes, it is unlikely) + Message += "\nFilename: " + flCombine("/nonexistent", Item.Owner->DestFile); + } + } + else + Message += "\nFilename: " + Item.Owner->DestFile; + Message += "\n\n"; + + if (Debug == true) + clog << " -> " << Access << ':' << QuoteString(Message, "\n") << endl; + OutQueue += Message; + OutReady = true; + + return true; +} + /*}}}*/ +// Worker::OutFdRead - Out bound FD is ready /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgAcquire::Worker::OutFdReady() +{ + int Res; + do + { + Res = write(OutFd,OutQueue.c_str(),OutQueue.length()); + } + while (Res < 0 && errno == EINTR); + + if (Res <= 0) + return MethodFailure(); + + OutQueue.erase(0,Res); + if (OutQueue.empty() == true) + OutReady = false; + + return true; +} + /*}}}*/ +// Worker::InFdRead - In bound FD is ready /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgAcquire::Worker::InFdReady() +{ + if (ReadMessages() == false) + return false; + RunMessages(); + return true; +} + /*}}}*/ +// Worker::MethodFailure - Called when the method fails /*{{{*/ +// --------------------------------------------------------------------- +/* This is called when the method is believed to have failed, probably because + read returned -1. */ +bool pkgAcquire::Worker::MethodFailure() +{ + _error->Error("Method %s has died unexpectedly!",Access.c_str()); + + // do not reap the child here to show meaningful error to the user + ExecWait(Process,Access.c_str(),false); + Process = -1; + close(InFd); + close(OutFd); + InFd = -1; + OutFd = -1; + OutReady = false; + InReady = false; + OutQueue = string(); + MessageQueue.erase(MessageQueue.begin(),MessageQueue.end()); + + return false; +} + /*}}}*/ +// Worker::Pulse - Called periodically /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgAcquire::Worker::Pulse() +{ + if (CurrentItem == 0) + return; + + struct stat Buf; + if (stat(CurrentItem->Owner->DestFile.c_str(),&Buf) != 0) + return; + CurrentSize = Buf.st_size; +} + /*}}}*/ +// Worker::ItemDone - Called when the current item is finished /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgAcquire::Worker::ItemDone() +{ + CurrentItem = 0; + CurrentSize = 0; + TotalSize = 0; + Status = string(); +} + /*}}}*/ +void pkgAcquire::Worker::PrepareFiles(char const * const caller, pkgAcquire::Queue::QItem const * const Itm)/*{{{*/ +{ + if (RealFileExists(Itm->Owner->DestFile)) + { + ChangeOwnerAndPermissionOfFile(caller, Itm->Owner->DestFile.c_str(), "root", ROOT_GROUP, 0644); + std::string const filename = Itm->Owner->DestFile; + for (pkgAcquire::Queue::QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O) + { + pkgAcquire::Item const * const Owner = *O; + if (Owner->DestFile == filename || filename == "/dev/null") + continue; + RemoveFile("PrepareFiles", Owner->DestFile); + if (link(filename.c_str(), Owner->DestFile.c_str()) != 0) + { + // different mounts can't happen for us as we download to lists/ by default, + // but if the system is reused by others the locations can potentially be on + // different disks, so use symlink as poor-men replacement. + // FIXME: Real copying as last fallback, but that is costly, so offload to a method preferable + if (symlink(filename.c_str(), Owner->DestFile.c_str()) != 0) + _error->Error("Can't create (sym)link of file %s to %s", filename.c_str(), Owner->DestFile.c_str()); + } + } + } + else + { + for (pkgAcquire::Queue::QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O) + RemoveFile("PrepareFiles", (*O)->DestFile); + } +} + /*}}}*/ diff --git a/apt-pkg/acquire-worker.h b/apt-pkg/acquire-worker.h new file mode 100644 index 0000000..11e54b0 --- /dev/null +++ b/apt-pkg/acquire-worker.h @@ -0,0 +1,339 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Acquire Worker - Worker process manager + + Each worker class is associated with exactly one subprocess. + + ##################################################################### */ + /*}}}*/ + +/** \addtogroup acquire + * @{ + * + * \file acquire-worker.h + */ + +#ifndef PKGLIB_ACQUIRE_WORKER_H +#define PKGLIB_ACQUIRE_WORKER_H + +#include <apt-pkg/acquire.h> +#include <apt-pkg/weakptr.h> + +#include <string> +#include <vector> +#include <sys/types.h> + +/** \brief A fetch subprocess. + * + * A worker process is responsible for one stage of the fetch. This + * class encapsulates the communications protocol between the master + * process and the worker, from the master end. + * + * Each worker is intrinsically placed on two linked lists. The + * Queue list (maintained in the #NextQueue variable) is maintained + * by the pkgAcquire::Queue class; it represents the set of workers + * assigned to a particular queue. The Acquire list (maintained in + * the #NextAcquire variable) is maintained by the pkgAcquire class; + * it represents the set of active workers for a particular + * pkgAcquire object. + * + * \todo Like everything else in the Acquire system, this has way too + * many protected items. + * + * \sa pkgAcqMethod, pkgAcquire::Item, pkgAcquire + */ +class pkgAcquire::Worker : public WeakPointable +{ + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + friend class pkgAcquire; + + protected: + friend class Queue; + + /** \brief The next link on the Queue list. + * + * \todo This is always NULL; is it just for future use? + */ + Worker *NextQueue; + + /** \brief The next link on the Acquire list. */ + Worker *NextAcquire; + + /** \brief The Queue with which this worker is associated. */ + Queue *OwnerQ; + + /** \brief The download progress indicator to which progress + * messages should be sent. + */ + pkgAcquireStatus *Log; + + /** \brief The configuration of this method. On startup, the + * target of this pointer is filled in with basic data about the + * method, as reported by the worker. + */ + MethodConfig *Config; + + /** \brief The access method to be used by this worker. + * + * \todo Doesn't this duplicate Config->Access? + */ + std::string Access; + + /** \brief The PID of the subprocess. */ + pid_t Process; + + /** \brief A file descriptor connected to the standard output of + * the subprocess. + * + * Used to read messages and data from the subprocess. + */ + int InFd; + + /** \brief A file descriptor connected to the standard input of the + * subprocess. + * + * Used to send commands and configuration data to the subprocess. + */ + int OutFd; + + /** \brief The socket to send SCM_RIGHTS message through + */ + int PrivSepSocketFd; + int PrivSepSocketFdChild; + + /** \brief Set to \b true if the worker is in a state in which it + * might generate data or command responses. + * + * \todo Is this right? It's a guess. + */ + bool InReady; + + /** \brief Set to \b true if the worker is in a state in which it + * is legal to send commands to it. + * + * \todo Is this right? + */ + bool OutReady; + + /** If \b true, debugging output will be sent to std::clog. */ + bool Debug; + + /** \brief The raw text values of messages received from the + * worker, in sequence. + */ + std::vector<std::string> MessageQueue; + + /** \brief Buffers pending writes to the subprocess. + * + * \todo Wouldn't a std::dequeue be more appropriate? + */ + std::string OutQueue; + + /** \brief Common code for the constructor. + * + * Initializes NextQueue and NextAcquire to NULL; Process, InFd, + * and OutFd to -1, OutReady and InReady to \b false, and Debug + * from _config. + */ + void Construct(); + + /** \brief Retrieve any available messages from the subprocess. + * + * The messages are retrieved as in \link strutl.h ReadMessages()\endlink, and + * #MethodFailure() is invoked if an error occurs; in particular, + * if the pipe to the subprocess dies unexpectedly while a message + * is being read. + * + * \return \b true if the messages were successfully read, \b + * false otherwise. + */ + bool ReadMessages(); + + /** \brief Parse and dispatch pending messages. + * + * This dispatches the message in a manner appropriate for its + * type. + * + * \todo Several message types lack separate handlers. + * + * \sa Capabilities(), SendConfiguration(), MediaChange() + */ + bool RunMessages(); + + /** \brief Read and dispatch any pending messages from the + * subprocess. + * + * \return \b false if the subprocess died unexpectedly while a + * message was being transmitted. + */ + bool InFdReady(); + + /** \brief Send any pending commands to the subprocess. + * + * This method will fail if there is no pending output. + * + * \return \b true if all commands were succeeded, \b false if an + * error occurred (in which case MethodFailure() will be invoked). + */ + bool OutFdReady(); + + /** \brief Handle a 100 Capabilities response from the subprocess. + * + * \param Message the raw text of the message from the subprocess. + * + * The message will be parsed and its contents used to fill + * #Config. If #Config is NULL, this routine is a NOP. + * + * \return \b true. + */ + bool Capabilities(std::string Message); + + /** \brief Send a 601 Configuration message (containing the APT + * configuration) to the subprocess. + * + * The APT configuration will be send to the subprocess in a + * message of the following form: + * + * <pre> + * 601 Configuration + * Config-Item: Fully-Qualified-Item=Val + * Config-Item: Fully-Qualified-Item=Val + * ... + * </pre> + * + * \return \b true if the command was successfully sent, \b false + * otherwise. + */ + bool SendConfiguration(); + + /** \brief Handle a 403 Media Change message. + * + * \param Message the raw text of the message; the Media field + * indicates what type of media should be changed, and the Drive + * field indicates where the media is located. + * + * Invokes pkgAcquireStatus::MediaChange(Media, Drive) to ask the + * user to swap disks; informs the subprocess of the result (via + * 603 Media Changed, with the Failed field set to \b true if the + * user cancelled the media change). + */ + bool MediaChange(std::string Message); + + /** \brief Invoked when the worked process dies unexpectedly. + * + * Waits for the subprocess to terminate and generates an error if + * it terminated abnormally, then closes and blanks out all file + * descriptors. Discards all pending messages from the + * subprocess. + * + * \return \b false. + */ + bool MethodFailure(); + + /** \brief Invoked when a fetch job is completed, either + * successfully or unsuccessfully. + * + * Resets the status information for the worker process. + */ + void ItemDone(); + + public: + + /** \brief The queue entry that is currently being downloaded. */ + pkgAcquire::Queue::QItem *CurrentItem; + + /** \brief The most recent status string received from the + * subprocess. + */ + std::string Status; + + /** \brief How many bytes of the file have been downloaded. Zero + * if the current progress of the file cannot be determined. + */ + unsigned long long CurrentSize; + + /** \brief The total number of bytes to be downloaded. Zero if the + * total size of the final is unknown. + */ + unsigned long long TotalSize; + + /** \brief How much of the file was already downloaded prior to + * starting this worker. + */ + unsigned long long ResumePoint; + + /** \brief Tell the subprocess to download the given item. + * + * \param Item the item to queue up. + * \return \b true if the item was successfully enqueued. + * + * Queues up a 600 URI Acquire message for the given item to be + * sent at the next possible moment. Does \e not flush the output + * queue. + */ + bool QueueItem(pkgAcquire::Queue::QItem *Item); + APT_HIDDEN bool ReplyAux(pkgAcquire::ItemDesc const &Item); + + /** \brief Start up the worker and fill in #Config. + * + * Reads the first message from the worker, which is assumed to be + * a 100 Capabilities message. + * + * \return \b true if all operations completed successfully. + */ + bool Start(); + + /** \brief Update the worker statistics (CurrentSize, TotalSize, + * etc). + */ + void Pulse(); + + /** \return The fetch method configuration. */ + inline const MethodConfig *GetConf() const {return Config;}; + + /** \brief Create a new Worker to download files. + * + * \param OwnerQ The queue into which this worker should be + * placed. + * + * \param Config A location in which to store information about + * the fetch method. + * + * \param Log The download progress indicator that should be used + * to report the progress of this worker. + */ + Worker(Queue *OwnerQ,MethodConfig *Config,pkgAcquireStatus *Log); + + /** \brief Create a new Worker that should just retrieve + * information about the fetch method. + * + * Nothing in particular forces you to refrain from actually + * downloading stuff, but the various status callbacks won't be + * invoked. + * + * \param Config A location in which to store information about + * the fetch method. + */ + explicit Worker(MethodConfig *Config); + + /** \brief Clean up this worker. + * + * Closes the file descriptors; if MethodConfig::NeedsCleanup is + * \b false, also rudely interrupts the worker with a SIGINT. + */ + virtual ~Worker(); + +private: + APT_HIDDEN void PrepareFiles(char const * const caller, pkgAcquire::Queue::QItem const * const Itm); + APT_HIDDEN void HandleFailure(std::vector<pkgAcquire::Item *> const &ItmOwners, + pkgAcquire::MethodConfig *const Config, pkgAcquireStatus *const Log, + std::string const &Message, bool const errTransient, bool const errAuthErr); +}; + +/** @} */ + +#endif diff --git a/apt-pkg/acquire.cc b/apt-pkg/acquire.cc new file mode 100644 index 0000000..541785b --- /dev/null +++ b/apt-pkg/acquire.cc @@ -0,0 +1,1486 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Acquire - File Acquiration + + The core element for the schedule system is the concept of a named + queue. Each queue is unique and each queue has a name derived from the + URI. The degree of paralization can be controlled by how the queue + name is derived from the URI. + + ##################################################################### */ + /*}}}*/ +// 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/fileutl.h> +#include <apt-pkg/strutl.h> + +#include <algorithm> +#include <chrono> +#include <iomanip> +#include <iostream> +#include <memory> +#include <numeric> +#include <sstream> +#include <string> +#include <vector> +#include <cmath> + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ + +using namespace std; + +// Acquire::pkgAcquire - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* We grab some runtime state from the configuration space */ +pkgAcquire::pkgAcquire() : LockFD(-1), d(NULL), Queues(0), Workers(0), Configs(0), Log(NULL), ToFetch(0), + Debug(_config->FindB("Debug::pkgAcquire",false)), + Running(false) +{ + Initialize(); +} +pkgAcquire::pkgAcquire(pkgAcquireStatus *Progress) : LockFD(-1), d(NULL), Queues(0), Workers(0), + Configs(0), Log(NULL), ToFetch(0), + Debug(_config->FindB("Debug::pkgAcquire",false)), + Running(false) +{ + Initialize(); + SetLog(Progress); +} +void pkgAcquire::Initialize() +{ + string const Mode = _config->Find("Acquire::Queue-Mode","host"); + if (strcasecmp(Mode.c_str(),"host") == 0) + QueueMode = QueueHost; + if (strcasecmp(Mode.c_str(),"access") == 0) + QueueMode = QueueAccess; +} + /*}}}*/ +// Acquire::GetLock - lock directory and prepare for action /*{{{*/ +static bool SetupAPTPartialDirectory(std::string const &grand, std::string const &parent, std::string const &postfix, mode_t const mode) +{ + if (_config->FindB("Debug::SetupAPTPartialDirectory::AssumeGood", false)) + return true; + std::string const partial = parent + postfix; + bool const partialExists = DirectoryExists(partial); + if (partialExists == false) + { + mode_t const old_umask = umask(S_IWGRP | S_IWOTH); + bool const creation_fail = (CreateAPTDirectoryIfNeeded(grand, partial) == false && + CreateAPTDirectoryIfNeeded(parent, partial) == false); + umask(old_umask); + if (creation_fail == true) + return false; + } + + std::string const SandboxUser = _config->Find("APT::Sandbox::User"); + if (getuid() == 0) + { + if (SandboxUser.empty() == false && SandboxUser != "root") // if we aren't root, we can't chown, so don't try it + { + struct passwd const * const pw = getpwnam(SandboxUser.c_str()); + struct group const * const gr = getgrnam(ROOT_GROUP); + if (pw != NULL && gr != NULL) + { + // chown the partial dir + if(chown(partial.c_str(), pw->pw_uid, gr->gr_gid) != 0) + _error->WarningE("SetupAPTPartialDirectory", "chown to %s:root of directory %s failed", SandboxUser.c_str(), partial.c_str()); + } + } + if (chmod(partial.c_str(), mode) != 0) + _error->WarningE("SetupAPTPartialDirectory", "chmod 0700 of directory %s failed", partial.c_str()); + + } + else if (chmod(partial.c_str(), mode) != 0) + { + // if we haven't created the dir and aren't root, it is kinda expected that chmod doesn't work + if (partialExists == false) + _error->WarningE("SetupAPTPartialDirectory", "chmod 0700 of directory %s failed", partial.c_str()); + } + + _error->PushToStack(); + // remove 'old' FAILED files to stop us from collecting them for no reason + for (auto const &Failed: GetListOfFilesInDir(partial, "FAILED", false, false)) + RemoveFile("SetupAPTPartialDirectory", Failed); + _error->RevertToStack(); + + return true; +} +bool pkgAcquire::Setup(pkgAcquireStatus *Progress, string const &Lock) +{ + Log = Progress; + if (Lock.empty()) + { + string const listDir = _config->FindDir("Dir::State::lists"); + if (SetupAPTPartialDirectory(_config->FindDir("Dir::State"), listDir, "partial", 0700) == false) + return _error->Errno("Acquire", _("List directory %s is missing."), (listDir + "partial").c_str()); + if (SetupAPTPartialDirectory(_config->FindDir("Dir::State"), listDir, "auxfiles", 0755) == false) + { + // not being able to create lists/auxfiles isn't critical as we will use a tmpdir then + } + string const archivesDir = _config->FindDir("Dir::Cache::Archives"); + if (SetupAPTPartialDirectory(_config->FindDir("Dir::Cache"), archivesDir, "partial", 0700) == false) + return _error->Errno("Acquire", _("Archives directory %s is missing."), (archivesDir + "partial").c_str()); + return true; + } + return GetLock(Lock); +} +bool pkgAcquire::GetLock(std::string const &Lock) +{ + if (Lock.empty() == true) + return false; + + // check for existence and possibly create auxiliary directories + string const listDir = _config->FindDir("Dir::State::lists"); + string const archivesDir = _config->FindDir("Dir::Cache::Archives"); + + if (Lock == listDir) + { + if (SetupAPTPartialDirectory(_config->FindDir("Dir::State"), listDir, "partial", 0700) == false) + return _error->Errno("Acquire", _("List directory %s is missing."), (listDir + "partial").c_str()); + } + if (Lock == archivesDir) + { + if (SetupAPTPartialDirectory(_config->FindDir("Dir::Cache"), archivesDir, "partial", 0700) == false) + return _error->Errno("Acquire", _("Archives directory %s is missing."), (archivesDir + "partial").c_str()); + } + if (Lock == listDir || Lock == archivesDir) + { + if (SetupAPTPartialDirectory(_config->FindDir("Dir::State"), listDir, "auxfiles", 0755) == false) + { + // not being able to create lists/auxfiles isn't critical as we will use a tmpdir then + } + } + + if (_config->FindB("Debug::NoLocking", false) == true) + return true; + + // Lock the directory this acquire object will work in + if (LockFD != -1) + close(LockFD); + LockFD = ::GetLock(flCombine(Lock, "lock")); + if (LockFD == -1) + return _error->Error(_("Unable to lock directory %s"), Lock.c_str()); + + return true; +} + /*}}}*/ +// Acquire::~pkgAcquire - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* Free our memory, clean up the queues (destroy the workers) */ +pkgAcquire::~pkgAcquire() +{ + Shutdown(); + + if (LockFD != -1) + close(LockFD); + + while (Configs != 0) + { + MethodConfig *Jnk = Configs; + Configs = Configs->Next; + delete Jnk; + } +} + /*}}}*/ +// Acquire::Shutdown - Clean out the acquire object /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgAcquire::Shutdown() +{ + while (Items.empty() == false) + { + if (Items[0]->Status == Item::StatFetching) + Items[0]->Status = Item::StatError; + delete Items[0]; + } + + while (Queues != 0) + { + Queue *Jnk = Queues; + Queues = Queues->Next; + delete Jnk; + } +} + /*}}}*/ +// Acquire::Add - Add a new item /*{{{*/ +// --------------------------------------------------------------------- +/* This puts an item on the acquire list. This list is mainly for tracking + item status */ +void pkgAcquire::Add(Item *Itm) +{ + Items.push_back(Itm); +} + /*}}}*/ +// Acquire::Remove - Remove a item /*{{{*/ +// --------------------------------------------------------------------- +/* Remove an item from the acquire list. This is usually not used.. */ +void pkgAcquire::Remove(Item *Itm) +{ + Dequeue(Itm); + + for (ItemIterator I = Items.begin(); I != Items.end();) + { + if (*I == Itm) + { + Items.erase(I); + I = Items.begin(); + } + else + ++I; + } +} + /*}}}*/ +// Acquire::Add - Add a worker /*{{{*/ +// --------------------------------------------------------------------- +/* A list of workers is kept so that the select loop can direct their FD + usage. */ +void pkgAcquire::Add(Worker *Work) +{ + Work->NextAcquire = Workers; + Workers = Work; +} + /*}}}*/ +// Acquire::Remove - Remove a worker /*{{{*/ +// --------------------------------------------------------------------- +/* A worker has died. This can not be done while the select loop is running + as it would require that RunFds could handling a changing list state and + it can't.. */ +void pkgAcquire::Remove(Worker *Work) +{ + if (Running == true) + abort(); + + Worker **I = &Workers; + for (; *I != 0;) + { + if (*I == Work) + *I = (*I)->NextAcquire; + else + I = &(*I)->NextAcquire; + } +} + /*}}}*/ +// Acquire::Enqueue - Queue an URI for fetching /*{{{*/ +// --------------------------------------------------------------------- +/* This is the entry point for an item. An item calls this function when + it is constructed which creates a queue (based on the current queue + mode) and puts the item in that queue. If the system is running then + the queue might be started. */ +static bool CheckForBadItemAndFailIt(pkgAcquire::Item * const Item, + pkgAcquire::MethodConfig const * const Config, pkgAcquireStatus * const Log) +{ + auto SavedDesc = Item->GetItemDesc(); + if (Item->IsRedirectionLoop(SavedDesc.URI)) + { + std::string const Message = "400 URI Failure" + "\nURI: " + SavedDesc.URI + + "\nFilename: " + Item->DestFile + + "\nFailReason: RedirectionLoop"; + + Item->Status = pkgAcquire::Item::StatError; + Item->Failed(Message, Config); + if (Log != nullptr) + Log->Fail(SavedDesc); + return true; + } + + HashStringList const hsl = Item->GetExpectedHashes(); + if (hsl.usable() == false && Item->HashesRequired() && + _config->Exists("Acquire::ForceHash") == false) + { + std::string const Message = "400 URI Failure" + "\nURI: " + SavedDesc.URI + + "\nFilename: " + Item->DestFile + + "\nFailReason: WeakHashSums"; + + Item->Status = pkgAcquire::Item::StatAuthError; + Item->Failed(Message, Config); + if (Log != nullptr) + Log->Fail(SavedDesc); + return true; + } + return false; +} +void pkgAcquire::Enqueue(ItemDesc &Item) +{ + // Determine which queue to put the item in + const MethodConfig *Config; + string Name = QueueName(Item.URI,Config); + if (Name.empty() == true) + { + Item.Owner->Status = pkgAcquire::Item::StatError; + return; + } + + /* the check for running avoids that we produce errors + in logging before we actually have started, which would + be easier to implement but would confuse users/implementations + so we check the items skipped here in #Startup */ + if (Running && CheckForBadItemAndFailIt(Item.Owner, Config, Log)) + return; + + // Find the queue structure + Queue *I = Queues; + for (; I != 0 && I->Name != Name; I = I->Next); + if (I == 0) + { + I = new Queue(Name,this); + I->Next = Queues; + Queues = I; + + if (Running == true) + I->Startup(); + } + + // See if this is a local only URI + if (Config->LocalOnly == true && Item.Owner->Complete == false) + Item.Owner->Local = true; + Item.Owner->Status = Item::StatIdle; + + // Queue it into the named queue + if(I->Enqueue(Item)) + ToFetch++; + + // Some trace stuff + if (Debug == true) + { + clog << "Fetching " << Item.URI << endl; + clog << " to " << Item.Owner->DestFile << endl; + clog << " Queue is: " << Name << endl; + } +} + /*}}}*/ +// Acquire::Dequeue - Remove an item from all queues /*{{{*/ +// --------------------------------------------------------------------- +/* This is called when an item is finished being fetched. It removes it + from all the queues */ +void pkgAcquire::Dequeue(Item *Itm) +{ + Queue *I = Queues; + bool Res = false; + if (Debug == true) + clog << "Dequeuing " << Itm->DestFile << endl; + + for (; I != 0; I = I->Next) + { + if (I->Dequeue(Itm)) + { + Res = true; + if (Debug == true) + clog << "Dequeued from " << I->Name << endl; + } + } + + if (Res == true) + ToFetch--; +} + /*}}}*/ +// Acquire::QueueName - Return the name of the queue for this URI /*{{{*/ +// --------------------------------------------------------------------- +/* The string returned depends on the configuration settings and the + method parameters. Given something like http://foo.org/bar it can + return http://foo.org or http */ +string pkgAcquire::QueueName(string Uri,MethodConfig const *&Config) +{ + URI U(Uri); + + Config = GetConfig(U.Access); + if (Config == 0) + return string(); + + /* Single-Instance methods get exactly one queue per URI. This is + also used for the Access queue method */ + if (Config->SingleInstance == true || QueueMode == QueueAccess) + return U.Access; + + string AccessSchema = U.Access + ':'; + string FullQueueName; + + if (U.Host.empty()) + { + long existing = 0; + // check how many queues exist already and reuse empty ones + for (Queue const *I = Queues; I != 0; I = I->Next) + if (I->Name.compare(0, AccessSchema.length(), AccessSchema) == 0) + { + if (I->Items == nullptr) + return I->Name; + ++existing; + } + +#ifdef _SC_NPROCESSORS_ONLN + long cpuCount = sysconf(_SC_NPROCESSORS_ONLN) * 2; +#else + long cpuCount = 10; +#endif + cpuCount = _config->FindI("Acquire::QueueHost::Limit", cpuCount); + + if (cpuCount <= 0 || existing < cpuCount) + strprintf(FullQueueName, "%s%ld", AccessSchema.c_str(), existing); + else + { + long const randomQueue = random() % cpuCount; + strprintf(FullQueueName, "%s%ld", AccessSchema.c_str(), randomQueue); + } + + if (Debug) + clog << "Chose random queue " << FullQueueName << " for " << Uri << endl; + } else + { + FullQueueName = AccessSchema + U.Host; + } + unsigned int Instances = 0, SchemaLength = AccessSchema.length(); + + Queue *I = Queues; + for (; I != 0; I = I->Next) { + // if the queue already exists, re-use it + if (I->Name == FullQueueName) + return FullQueueName; + + if (I->Name.compare(0, SchemaLength, AccessSchema) == 0) + Instances++; + } + + if (Debug) { + clog << "Found " << Instances << " instances of " << U.Access << endl; + } + + if (Instances >= static_cast<decltype(Instances)>(_config->FindI("Acquire::QueueHost::Limit",10))) + return U.Access; + + return FullQueueName; +} + /*}}}*/ +// Acquire::GetConfig - Fetch the configuration information /*{{{*/ +// --------------------------------------------------------------------- +/* This locates the configuration structure for an access method. If + a config structure cannot be found a Worker will be created to + retrieve it */ +pkgAcquire::MethodConfig *pkgAcquire::GetConfig(string Access) +{ + // Search for an existing config + MethodConfig *Conf; + for (Conf = Configs; Conf != 0; Conf = Conf->Next) + if (Conf->Access == Access) + return Conf; + + // Create the new config class + Conf = new MethodConfig; + Conf->Access = Access; + + // Create the worker to fetch the configuration + Worker Work(Conf); + if (Work.Start() == false) + { + delete Conf; + return nullptr; + } + Conf->Next = Configs; + Configs = Conf; + + /* if a method uses DownloadLimit, we switch to SingleInstance mode */ + if(_config->FindI("Acquire::"+Access+"::Dl-Limit",0) > 0) + Conf->SingleInstance = true; + + return Conf; +} + /*}}}*/ +// Acquire::SetFds - Deal with readable FDs /*{{{*/ +// --------------------------------------------------------------------- +/* Collect FDs that have activity monitors into the fd sets */ +void pkgAcquire::SetFds(int &Fd,fd_set *RSet,fd_set *WSet) +{ + for (Worker *I = Workers; I != 0; I = I->NextAcquire) + { + if (I->InReady == true && I->InFd >= 0) + { + if (Fd < I->InFd) + Fd = I->InFd; + FD_SET(I->InFd,RSet); + } + if (I->OutReady == true && I->OutFd >= 0) + { + if (Fd < I->OutFd) + Fd = I->OutFd; + FD_SET(I->OutFd,WSet); + } + } +} + /*}}}*/ +// Acquire::RunFds - compatibility remove on next abi/api break /*{{{*/ +void pkgAcquire::RunFds(fd_set *RSet,fd_set *WSet) +{ + RunFdsSane(RSet, WSet); +} + /*}}}*/ +// Acquire::RunFdsSane - Deal with active FDs /*{{{*/ +// --------------------------------------------------------------------- +/* Dispatch active FDs over to the proper workers. It is very important + that a worker never be erased while this is running! The queue class + should never erase a worker except during shutdown processing. */ +bool pkgAcquire::RunFdsSane(fd_set *RSet,fd_set *WSet) +{ + bool Res = true; + + for (Worker *I = Workers; I != 0; I = I->NextAcquire) + { + if (I->InFd >= 0 && FD_ISSET(I->InFd,RSet) != 0) + Res &= I->InFdReady(); + if (I->OutFd >= 0 && FD_ISSET(I->OutFd,WSet) != 0) + Res &= I->OutFdReady(); + } + + return Res; +} + /*}}}*/ +// Acquire::Run - Run the fetch sequence /*{{{*/ +// --------------------------------------------------------------------- +/* This runs the queues. It manages a select loop for all of the + Worker tasks. The workers interact with the queues and items to + manage the actual fetch. */ +static bool IsAccessibleBySandboxUser(std::string const &filename, bool const ReadWrite) +{ + // you would think this is easily to answer with faccessat, right? Wrong! + // It e.g. gets groups wrong, so the only thing which works reliable is trying + // to open the file we want to open later on… + if (unlikely(filename.empty())) + return true; + + if (ReadWrite == false) + { + errno = 0; + // can we read a file? Note that non-existing files are "fine" + int const fd = open(filename.c_str(), O_RDONLY | O_CLOEXEC); + if (fd == -1 && errno == EACCES) + return false; + close(fd); + return true; + } + else + { + // the file might not exist yet and even if it does we will fix permissions, + // so important is here just that the directory it is in allows that + std::string const dirname = flNotFile(filename); + if (unlikely(dirname.empty())) + return true; + + char const * const filetag = ".apt-acquire-privs-test.XXXXXX"; + std::string const tmpfile_tpl = flCombine(dirname, filetag); + std::unique_ptr<char, decltype(std::free) *> tmpfile { strdup(tmpfile_tpl.c_str()), std::free }; + int const fd = mkstemp(tmpfile.get()); + if (fd == -1 && errno == EACCES) + return false; + RemoveFile("IsAccessibleBySandboxUser", tmpfile.get()); + close(fd); + return true; + } +} +static void CheckDropPrivsMustBeDisabled(pkgAcquire const &Fetcher) +{ + if(getuid() != 0) + return; + + std::string const SandboxUser = _config->Find("APT::Sandbox::User"); + if (SandboxUser.empty() || SandboxUser == "root") + return; + + struct passwd const * const pw = getpwnam(SandboxUser.c_str()); + if (pw == NULL) + { + _error->Warning(_("No sandbox user '%s' on the system, can not drop privileges"), SandboxUser.c_str()); + _config->Set("APT::Sandbox::User", ""); + return; + } + + gid_t const old_euid = geteuid(); + gid_t const old_egid = getegid(); + + long const ngroups_max = sysconf(_SC_NGROUPS_MAX); + std::unique_ptr<gid_t[]> old_gidlist(new gid_t[ngroups_max]); + if (unlikely(old_gidlist == NULL)) + return; + ssize_t old_gidlist_nr; + if ((old_gidlist_nr = getgroups(ngroups_max, old_gidlist.get())) < 0) + { + _error->FatalE("getgroups", "getgroups %lu failed", ngroups_max); + old_gidlist[0] = 0; + old_gidlist_nr = 1; + } + if (setgroups(1, &pw->pw_gid)) + _error->FatalE("setgroups", "setgroups %u failed", pw->pw_gid); + + if (setegid(pw->pw_gid) != 0) + _error->FatalE("setegid", "setegid %u failed", pw->pw_gid); + if (seteuid(pw->pw_uid) != 0) + _error->FatalE("seteuid", "seteuid %u failed", pw->pw_uid); + + for (pkgAcquire::ItemCIterator I = Fetcher.ItemsBegin(); + I != Fetcher.ItemsEnd(); ++I) + { + // no need to drop privileges for a complete file + if ((*I)->Complete == true || (*I)->Status != pkgAcquire::Item::StatIdle) + continue; + + // if destination file is inaccessible all hope is lost for privilege dropping + if (IsAccessibleBySandboxUser((*I)->DestFile, true) == false) + { + _error->WarningE("pkgAcquire::Run", _("Download is performed unsandboxed as root as file '%s' couldn't be accessed by user '%s'."), + (*I)->DestFile.c_str(), SandboxUser.c_str()); + _config->Set("APT::Sandbox::User", ""); + break; + } + + // if its the source file (e.g. local sources) we might be lucky + // by dropping the dropping only for some methods. + URI const source = (*I)->DescURI(); + if (source.Access == "file" || source.Access == "copy") + { + std::string const conf = "Binary::" + source.Access + "::APT::Sandbox::User"; + if (_config->Exists(conf) == true) + continue; + + if (IsAccessibleBySandboxUser(source.Path, false) == false) + { + _error->NoticeE("pkgAcquire::Run", _("Download is performed unsandboxed as root as file '%s' couldn't be accessed by user '%s'."), + source.Path.c_str(), SandboxUser.c_str()); + _config->CndSet("Binary::file::APT::Sandbox::User", "root"); + _config->CndSet("Binary::copy::APT::Sandbox::User", "root"); + } + } + } + + if (seteuid(old_euid) != 0) + _error->FatalE("seteuid", "seteuid %u failed", old_euid); + if (setegid(old_egid) != 0) + _error->FatalE("setegid", "setegid %u failed", old_egid); + if (setgroups(old_gidlist_nr, old_gidlist.get())) + _error->FatalE("setgroups", "setgroups %u failed", 0); +} +pkgAcquire::RunResult pkgAcquire::Run(int PulseIntervall) +{ + _error->PushToStack(); + CheckDropPrivsMustBeDisabled(*this); + + Running = true; + + for (Queue *I = Queues; I != 0; I = I->Next) + I->Startup(); + + if (Log != 0) + Log->Start(); + + bool WasCancelled = false; + + // Run till all things have been acquired + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = PulseIntervall; + while (ToFetch > 0) + { + fd_set RFds; + fd_set WFds; + int Highest = 0; + FD_ZERO(&RFds); + FD_ZERO(&WFds); + SetFds(Highest,&RFds,&WFds); + + int Res; + do + { + Res = select(Highest+1,&RFds,&WFds,0,&tv); + } + while (Res < 0 && errno == EINTR); + + if (Res < 0) + { + _error->Errno("select","Select has failed"); + break; + } + + if(RunFdsSane(&RFds,&WFds) == false) + break; + + // Timeout, notify the log class + if (Res == 0 || (Log != 0 && Log->Update == true)) + { + tv.tv_usec = PulseIntervall; + for (Worker *I = Workers; I != 0; I = I->NextAcquire) + I->Pulse(); + if (Log != 0 && Log->Pulse(this) == false) + { + WasCancelled = true; + break; + } + } + } + + if (Log != 0) + Log->Stop(); + + // Shut down the acquire bits + Running = false; + for (Queue *I = Queues; I != 0; I = I->Next) + I->Shutdown(false); + + // Shut down the items + for (ItemIterator I = Items.begin(); I != Items.end(); ++I) + (*I)->Finished(); + + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + if (newError) + return Failed; + if (WasCancelled) + return Cancelled; + return Continue; +} + /*}}}*/ +// Acquire::Bump - Called when an item is dequeued /*{{{*/ +// --------------------------------------------------------------------- +/* This routine bumps idle queues in hopes that they will be able to fetch + the dequeued item */ +void pkgAcquire::Bump() +{ + for (Queue *I = Queues; I != 0; I = I->Next) + I->Bump(); +} + /*}}}*/ +// Acquire::WorkerStep - Step to the next worker /*{{{*/ +// --------------------------------------------------------------------- +/* Not inlined to advoid including acquire-worker.h */ +pkgAcquire::Worker *pkgAcquire::WorkerStep(Worker *I) +{ + return I->NextAcquire; +} + /*}}}*/ +// Acquire::Clean - Cleans a directory /*{{{*/ +// --------------------------------------------------------------------- +/* This is a bit simplistic, it looks at every file in the dir and sees + if it is part of the download set. */ +bool pkgAcquire::Clean(string Dir) +{ + // non-existing directories are by definition clean… + if (DirectoryExists(Dir) == false) + return true; + + if(Dir == "/") + return _error->Error(_("Clean of %s is not supported"), Dir.c_str()); + + int const dirfd = open(Dir.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (dirfd == -1) + return _error->Errno("open",_("Unable to read %s"),Dir.c_str()); + DIR * const D = fdopendir(dirfd); + if (D == nullptr) + return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str()); + + for (struct dirent *E = readdir(D); E != nullptr; E = readdir(D)) + { + // Skip some entries + if (strcmp(E->d_name, "lock") == 0 || + strcmp(E->d_name, "partial") == 0 || + strcmp(E->d_name, "auxfiles") == 0 || + strcmp(E->d_name, "lost+found") == 0 || + strcmp(E->d_name, ".") == 0 || + strcmp(E->d_name, "..") == 0) + continue; + + // Look in the get list and if not found nuke + if (std::any_of(Items.cbegin(), Items.cend(), + [&E](pkgAcquire::Item const * const I) { + return flNotDir(I->DestFile) == E->d_name; + }) == false) + { + RemoveFileAt("pkgAcquire::Clean", dirfd, E->d_name); + } + } + closedir(D); + return true; +} + /*}}}*/ +// Acquire::TotalNeeded - Number of bytes to fetch /*{{{*/ +// --------------------------------------------------------------------- +/* This is the total number of bytes needed */ +APT_PURE unsigned long long pkgAcquire::TotalNeeded() +{ + return std::accumulate(ItemsBegin(), ItemsEnd(), 0llu, + [](unsigned long long const T, Item const * const I) { + return T + I->FileSize; + }); +} + /*}}}*/ +// Acquire::FetchNeeded - Number of bytes needed to get /*{{{*/ +// --------------------------------------------------------------------- +/* This is the number of bytes that is not local */ +APT_PURE unsigned long long pkgAcquire::FetchNeeded() +{ + return std::accumulate(ItemsBegin(), ItemsEnd(), 0llu, + [](unsigned long long const T, Item const * const I) { + if (I->Local == false) + return T + I->FileSize; + else + return T; + }); +} + /*}}}*/ +// Acquire::PartialPresent - Number of partial bytes we already have /*{{{*/ +// --------------------------------------------------------------------- +/* This is the number of bytes that is not local */ +APT_PURE unsigned long long pkgAcquire::PartialPresent() +{ + return std::accumulate(ItemsBegin(), ItemsEnd(), 0llu, + [](unsigned long long const T, Item const * const I) { + if (I->Local == false) + return T + I->PartialSize; + else + return T; + }); +} + /*}}}*/ +// Acquire::UriBegin - Start iterator for the uri list /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgAcquire::UriIterator pkgAcquire::UriBegin() +{ + return UriIterator(Queues); +} + /*}}}*/ +// Acquire::UriEnd - End iterator for the uri list /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgAcquire::UriIterator pkgAcquire::UriEnd() +{ + return UriIterator(0); +} + /*}}}*/ +// Acquire::MethodConfig::MethodConfig - Constructor /*{{{*/ +class pkgAcquire::MethodConfig::Private +{ + public: + bool AuxRequests = false; +}; +pkgAcquire::MethodConfig::MethodConfig() : d(new Private()), Next(0), SingleInstance(false), + Pipeline(false), SendConfig(false), LocalOnly(false), NeedsCleanup(false), + Removable(false) +{ +} + /*}}}*/ +bool pkgAcquire::MethodConfig::GetAuxRequests() const /*{{{*/ +{ + return d->AuxRequests; +} + /*}}}*/ +void pkgAcquire::MethodConfig::SetAuxRequests(bool const value) /*{{{*/ +{ + d->AuxRequests = value; +} + /*}}}*/ +// Queue::Queue - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgAcquire::Queue::Queue(string const &name,pkgAcquire * const owner) : d(NULL), Next(0), + Name(name), Items(0), Workers(0), Owner(owner), PipeDepth(0), MaxPipeDepth(1) +{ +} + /*}}}*/ +// Queue::~Queue - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgAcquire::Queue::~Queue() +{ + Shutdown(true); + + while (Items != 0) + { + QItem *Jnk = Items; + Items = Items->Next; + delete Jnk; + } +} + /*}}}*/ +// Queue::Enqueue - Queue an item to the queue /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgAcquire::Queue::Enqueue(ItemDesc &Item) +{ + // MetaKeysMatch checks whether the two items have no non-matching + // meta-keys. If the items are not transaction items, it returns + // true, so other items can still be merged. + auto MetaKeysMatch = [](pkgAcquire::ItemDesc const &A, pkgAcquire::Queue::QItem const *B) { + auto OwnerA = dynamic_cast<pkgAcqTransactionItem*>(A.Owner); + if (OwnerA == nullptr) + return true; + + for (auto const & OwnerBUncast : B->Owners) { + auto OwnerB = dynamic_cast<pkgAcqTransactionItem*>(OwnerBUncast); + + if (OwnerB != nullptr && OwnerA->GetMetaKey() != OwnerB->GetMetaKey()) + return false; + } + return true; + }; + QItem **OptimalI = &Items; + QItem **I = &Items; + // move to the end of the queue and check for duplicates here + for (; *I != 0; ) { + if (Item.URI == (*I)->URI && MetaKeysMatch(Item, *I)) + { + if (_config->FindB("Debug::pkgAcquire::Worker",false) == true) + std::cerr << " @ Queue: Action combined for " << Item.URI << " and " << (*I)->URI << std::endl; + (*I)->Owners.push_back(Item.Owner); + Item.Owner->Status = (*I)->Owner->Status; + return false; + } + // Determine the optimal position to insert: before anything with a + // higher priority. + int priority = (*I)->GetPriority(); + + I = &(*I)->Next; + if (priority >= Item.Owner->Priority()) { + OptimalI = I; + } + } + + + // Create a new item + QItem *Itm = new QItem; + *Itm = Item; + Itm->Next = *OptimalI; + *OptimalI = Itm; + + Item.Owner->QueueCounter++; + if (Items->Next == 0) + Cycle(); + return true; +} + /*}}}*/ +// Queue::Dequeue - Remove an item from the queue /*{{{*/ +// --------------------------------------------------------------------- +/* We return true if we hit something */ +bool pkgAcquire::Queue::Dequeue(Item *Owner) +{ + if (Owner->Status == pkgAcquire::Item::StatFetching) + return _error->Error("Tried to dequeue a fetching object"); + + bool Res = false; + + QItem **I = &Items; + for (; *I != 0;) + { + if (Owner == (*I)->Owner) + { + QItem *Jnk= *I; + *I = (*I)->Next; + Owner->QueueCounter--; + delete Jnk; + Res = true; + } + else + I = &(*I)->Next; + } + + return Res; +} + /*}}}*/ +// Queue::Startup - Start the worker processes /*{{{*/ +// --------------------------------------------------------------------- +/* It is possible for this to be called with a pre-existing set of + workers. */ +bool pkgAcquire::Queue::Startup() +{ + if (Workers == 0) + { + URI U(Name); + pkgAcquire::MethodConfig * const Cnf = Owner->GetConfig(U.Access); + if (unlikely(Cnf == nullptr)) + return false; + + // now-running twin of the pkgAcquire::Enqueue call + for (QItem *I = Items; I != 0; ) + { + auto const INext = I->Next; + for (auto &&O: I->Owners) + CheckForBadItemAndFailIt(O, Cnf, Owner->Log); + // if an item failed, it will be auto-dequeued invalidation our I here + I = INext; + } + + Workers = new Worker(this,Cnf,Owner->Log); + Owner->Add(Workers); + if (Workers->Start() == false) + return false; + + /* When pipelining we commit 10 items. This needs to change when we + added other source retry to have cycle maintain a pipeline depth + on its own. */ + if (Cnf->Pipeline == true) + MaxPipeDepth = _config->FindI("Acquire::Max-Pipeline-Depth",10); + else + MaxPipeDepth = 1; + } + + return Cycle(); +} + /*}}}*/ +// Queue::Shutdown - Shutdown the worker processes /*{{{*/ +// --------------------------------------------------------------------- +/* If final is true then all workers are eliminated, otherwise only workers + that do not need cleanup are removed */ +bool pkgAcquire::Queue::Shutdown(bool Final) +{ + // Delete all of the workers + pkgAcquire::Worker **Cur = &Workers; + while (*Cur != 0) + { + pkgAcquire::Worker *Jnk = *Cur; + if (Final == true || Jnk->GetConf()->NeedsCleanup == false) + { + *Cur = Jnk->NextQueue; + Owner->Remove(Jnk); + delete Jnk; + } + else + Cur = &(*Cur)->NextQueue; + } + + return true; +} + /*}}}*/ +// Queue::FindItem - Find a URI in the item list /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgAcquire::Queue::QItem *pkgAcquire::Queue::FindItem(string URI,pkgAcquire::Worker *Owner) +{ + for (QItem *I = Items; I != 0; I = I->Next) + if (I->URI == URI && I->Worker == Owner) + return I; + return 0; +} + /*}}}*/ +// Queue::ItemDone - Item has been completed /*{{{*/ +// --------------------------------------------------------------------- +/* The worker signals this which causes the item to be removed from the + queue. If this is the last queue instance then it is removed from the + main queue too.*/ +bool pkgAcquire::Queue::ItemDone(QItem *Itm) +{ + PipeDepth--; + for (QItem::owner_iterator O = Itm->Owners.begin(); O != Itm->Owners.end(); ++O) + { + if ((*O)->Status == pkgAcquire::Item::StatFetching) + (*O)->Status = pkgAcquire::Item::StatDone; + } + + if (Itm->Owner->QueueCounter <= 1) + Owner->Dequeue(Itm->Owner); + else + { + Dequeue(Itm->Owner); + Owner->Bump(); + } + + return Cycle(); +} + /*}}}*/ +// Queue::Cycle - Queue new items into the method /*{{{*/ +// --------------------------------------------------------------------- +/* This locates a new idle item and sends it to the worker. If pipelining + is enabled then it keeps the pipe full. */ +bool pkgAcquire::Queue::Cycle() +{ + if (Items == 0 || Workers == 0) + return true; + + if (PipeDepth < 0) + return _error->Error("Pipedepth failure"); + + // Look for a queable item + QItem *I = Items; + int ActivePriority = 0; + while (PipeDepth < static_cast<decltype(PipeDepth)>(MaxPipeDepth)) + { + for (; I != 0; I = I->Next) { + if (I->Owner->Status == pkgAcquire::Item::StatFetching) + ActivePriority = std::max(ActivePriority, I->GetPriority()); + if (I->Owner->Status == pkgAcquire::Item::StatIdle) + break; + } + + // Nothing to do, queue is idle. + if (I == 0) + return true; + + // This item has a lower priority than stuff in the pipeline, pretend + // the queue is idle + if (I->GetPriority() < ActivePriority) + return true; + I->Worker = Workers; + for (auto const &O: I->Owners) + O->Status = pkgAcquire::Item::StatFetching; + PipeDepth++; + if (Workers->QueueItem(I) == false) + return false; + } + + return true; +} + /*}}}*/ +// Queue::Bump - Fetch any pending objects if we are idle /*{{{*/ +// --------------------------------------------------------------------- +/* This is called when an item in multiple queues is dequeued */ +void pkgAcquire::Queue::Bump() +{ + Cycle(); +} + /*}}}*/ +HashStringList pkgAcquire::Queue::QItem::GetExpectedHashes() const /*{{{*/ +{ + /* each Item can have multiple owners and each owner might have different + hashes, even if that is unlikely in practice and if so at least some + owners will later fail. There is one situation through which is not a + failure and still needs this handling: Two owners who expect the same + file, but one owner only knows the SHA1 while the other only knows SHA256. */ + HashStringList superhsl; + for (pkgAcquire::Queue::QItem::owner_iterator O = Owners.begin(); O != Owners.end(); ++O) + { + HashStringList const hsl = (*O)->GetExpectedHashes(); + // we merge both lists - if we find disagreement send no hashes + HashStringList::const_iterator hs = hsl.begin(); + for (; hs != hsl.end(); ++hs) + if (superhsl.push_back(*hs) == false) + break; + if (hs != hsl.end()) + { + superhsl.clear(); + break; + } + } + return superhsl; +} + /*}}}*/ +APT_PURE unsigned long long pkgAcquire::Queue::QItem::GetMaximumSize() const /*{{{*/ +{ + unsigned long long Maximum = std::numeric_limits<unsigned long long>::max(); + for (auto const &O: Owners) + { + if (O->FileSize == 0) + continue; + Maximum = std::min(Maximum, O->FileSize); + } + if (Maximum == std::numeric_limits<unsigned long long>::max()) + return 0; + return Maximum; +} + /*}}}*/ +APT_PURE int pkgAcquire::Queue::QItem::GetPriority() const /*{{{*/ +{ + int Priority = 0; + for (auto const &O: Owners) + Priority = std::max(Priority, O->Priority()); + + return Priority; +} + /*}}}*/ +void pkgAcquire::Queue::QItem::SyncDestinationFiles() const /*{{{*/ +{ + /* ensure that the first owner has the best partial file of all and + the rest have (potentially dangling) symlinks to it so that + everything (like progress reporting) finds it easily */ + std::string superfile = Owner->DestFile; + off_t supersize = 0; + for (pkgAcquire::Queue::QItem::owner_iterator O = Owners.begin(); O != Owners.end(); ++O) + { + if ((*O)->DestFile == superfile) + continue; + struct stat file; + if (lstat((*O)->DestFile.c_str(),&file) == 0) + { + if ((file.st_mode & S_IFREG) == 0) + RemoveFile("SyncDestinationFiles", (*O)->DestFile); + else if (supersize < file.st_size) + { + supersize = file.st_size; + RemoveFile("SyncDestinationFiles", superfile); + rename((*O)->DestFile.c_str(), superfile.c_str()); + } + else + RemoveFile("SyncDestinationFiles", (*O)->DestFile); + if (symlink(superfile.c_str(), (*O)->DestFile.c_str()) != 0) + { + ; // not a problem per-se and no real alternative + } + } + } +} + /*}}}*/ +std::string pkgAcquire::Queue::QItem::Custom600Headers() const /*{{{*/ +{ + /* The others are relatively easy to merge, but this one? + Lets not merge and see how far we can run with it… + Likely, nobody will ever notice as all the items will + be of the same class and hence generate the same headers. */ + return Owner->Custom600Headers(); +} + /*}}}*/ + +// AcquireStatus::pkgAcquireStatus - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgAcquireStatus::pkgAcquireStatus() : d(NULL), Percent(-1), Update(true), MorePulses(false) +{ + Start(); +} + /*}}}*/ +// AcquireStatus::Pulse - Called periodically /*{{{*/ +// --------------------------------------------------------------------- +/* This computes some internal state variables for the derived classes to + use. It generates the current downloaded bytes and total bytes to download + as well as the current CPS estimate. */ +static struct timeval GetTimevalFromSteadyClock() +{ + auto const Time = std::chrono::steady_clock::now().time_since_epoch(); + auto const Time_sec = std::chrono::duration_cast<std::chrono::seconds>(Time); + auto const Time_usec = std::chrono::duration_cast<std::chrono::microseconds>(Time - Time_sec); + return { Time_sec.count(), Time_usec.count() }; +} +bool pkgAcquireStatus::Pulse(pkgAcquire *Owner) +{ + TotalBytes = 0; + CurrentBytes = 0; + TotalItems = 0; + CurrentItems = 0; + + // Compute the total number of bytes to fetch + unsigned int Unknown = 0; + unsigned int Count = 0; + bool ExpectAdditionalItems = false; + for (pkgAcquire::ItemCIterator I = Owner->ItemsBegin(); + I != Owner->ItemsEnd(); + ++I, ++Count) + { + TotalItems++; + if ((*I)->Status == pkgAcquire::Item::StatDone) + ++CurrentItems; + + // do we expect to acquire more files than we know of yet? + if ((*I)->ExpectedAdditionalItems > 0) + ExpectAdditionalItems = true; + + TotalBytes += (*I)->FileSize; + if ((*I)->Complete == true) + CurrentBytes += (*I)->FileSize; + if ((*I)->FileSize == 0 && (*I)->Complete == false) + ++Unknown; + } + + // Compute the current completion + unsigned long long ResumeSize = 0; + for (pkgAcquire::Worker *I = Owner->WorkersBegin(); I != 0; + I = Owner->WorkerStep(I)) + { + if (I->CurrentItem != 0 && I->CurrentItem->Owner->Complete == false) + { + CurrentBytes += I->CurrentSize; + ResumeSize += I->ResumePoint; + + // Files with unknown size always have 100% completion + if (I->CurrentItem->Owner->FileSize == 0 && + I->CurrentItem->Owner->Complete == false) + TotalBytes += I->CurrentSize; + } + } + + // Normalize the figures and account for unknown size downloads + if (TotalBytes <= 0) + TotalBytes = 1; + if (Unknown == Count) + TotalBytes = Unknown; + + // Wha?! Is not supposed to happen. + if (CurrentBytes > TotalBytes) + CurrentBytes = TotalBytes; + + // Compute the CPS + struct timeval NewTime = GetTimevalFromSteadyClock(); + + if ((NewTime.tv_sec - Time.tv_sec == 6 && NewTime.tv_usec > Time.tv_usec) || + NewTime.tv_sec - Time.tv_sec > 6) + { + std::chrono::duration<double> Delta = + std::chrono::seconds(NewTime.tv_sec - Time.tv_sec) + + std::chrono::microseconds(NewTime.tv_usec - Time.tv_usec); + + // Compute the CPS value + if (Delta < std::chrono::milliseconds(10)) + CurrentCPS = 0; + else + CurrentCPS = ((CurrentBytes - ResumeSize) - LastBytes)/ Delta.count(); + LastBytes = CurrentBytes - ResumeSize; + ElapsedTime = llround(Delta.count()); + Time = NewTime; + } + + double const OldPercent = Percent; + // calculate the percentage, if we have too little data assume 1% + if (ExpectAdditionalItems) + Percent = 0; + else + // use both files and bytes because bytes can be unreliable + Percent = (0.8 * (CurrentBytes/double(TotalBytes)*100.0) + + 0.2 * (CurrentItems/double(TotalItems)*100.0)); + + // debug + if (_config->FindB("Debug::acquire::progress", false) == true) + { + std::clog + << "[" + << std::setw(5) << std::setprecision(4) << std::showpoint << Percent + << "]" + << " Bytes: " + << SizeToStr(CurrentBytes) << " / " << SizeToStr(TotalBytes) + << " # Files: " + << CurrentItems << " / " << TotalItems + << std::endl; + } + + double const DiffPercent = Percent - OldPercent; + if (DiffPercent < 0.001 && _config->FindB("Acquire::Progress::Diffpercent", false) == true) + return true; + + int fd = _config->FindI("APT::Status-Fd",-1); + if(fd > 0) + { + ostringstream status; + + unsigned long long ETA = 0; + if(CurrentCPS > 0 && TotalBytes > CurrentBytes) + ETA = (TotalBytes - CurrentBytes) / CurrentCPS; + + std::string msg; + long i = CurrentItems < TotalItems ? CurrentItems + 1 : CurrentItems; + // only show the ETA if it makes sense + if (ETA > 0 && ETA < std::chrono::seconds(std::chrono::hours(24 * 2)).count()) + strprintf(msg, _("Retrieving file %li of %li (%s remaining)"), i, TotalItems, TimeToStr(ETA).c_str()); + else + strprintf(msg, _("Retrieving file %li of %li"), i, TotalItems); + + // build the status str + std::ostringstream str; + str.imbue(std::locale::classic()); + str.precision(4); + str << "dlstatus" << ':' << std::fixed << i << ':' << Percent << ':' << msg << '\n'; + auto const dlstatus = str.str(); + FileFd::Write(fd, dlstatus.data(), dlstatus.size()); + } + + return true; +} + /*}}}*/ +// AcquireStatus::Start - Called when the download is started /*{{{*/ +// --------------------------------------------------------------------- +/* We just reset the counters */ +void pkgAcquireStatus::Start() +{ + Time = StartTime = GetTimevalFromSteadyClock(); + LastBytes = 0; + CurrentCPS = 0; + CurrentBytes = 0; + TotalBytes = 0; + FetchedBytes = 0; + ElapsedTime = 0; + TotalItems = 0; + CurrentItems = 0; +} + /*}}}*/ +// AcquireStatus::Stop - Finished downloading /*{{{*/ +// --------------------------------------------------------------------- +/* This accurately computes the elapsed time and the total overall CPS. */ +void pkgAcquireStatus::Stop() +{ + // Compute the CPS and elapsed time + struct timeval NewTime = GetTimevalFromSteadyClock(); + + std::chrono::duration<double> Delta = + std::chrono::seconds(NewTime.tv_sec - StartTime.tv_sec) + + std::chrono::microseconds(NewTime.tv_usec - StartTime.tv_usec); + + // Compute the CPS value + if (Delta < std::chrono::milliseconds(10)) + CurrentCPS = 0; + else + CurrentCPS = FetchedBytes / Delta.count(); + LastBytes = CurrentBytes; + ElapsedTime = llround(Delta.count()); +} + /*}}}*/ +// AcquireStatus::Fetched - Called when a byte set has been fetched /*{{{*/ +// --------------------------------------------------------------------- +/* This is used to get accurate final transfer rate reporting. */ +void pkgAcquireStatus::Fetched(unsigned long long Size,unsigned long long Resume) +{ + FetchedBytes += Size - Resume; +} + /*}}}*/ +bool pkgAcquireStatus::ReleaseInfoChanges(metaIndex const * const LastRelease, metaIndex const * const CurrentRelease, std::vector<ReleaseInfoChange> &&Changes)/*{{{*/ +{ + auto const virt = dynamic_cast<pkgAcquireStatus2*>(this); + if (virt != nullptr) + return virt->ReleaseInfoChanges(LastRelease, CurrentRelease, std::move(Changes)); + return ReleaseInfoChangesAsGlobalErrors(std::move(Changes)); +} + /*}}}*/ +bool pkgAcquireStatus::ReleaseInfoChangesAsGlobalErrors(std::vector<ReleaseInfoChange> &&Changes)/*{{{*/ +{ + bool AllOkay = true; + for (auto const &c: Changes) + if (c.DefaultAction) + _error->Notice("%s", c.Message.c_str()); + else + { + _error->Error("%s", c.Message.c_str()); + AllOkay = false; + } + return AllOkay; +} + /*}}}*/ +bool pkgAcquireStatus2::ReleaseInfoChanges(metaIndex const * const, metaIndex const * const, std::vector<ReleaseInfoChange> &&Changes) +{ + return ReleaseInfoChangesAsGlobalErrors(std::move(Changes)); +} +pkgAcquireStatus2::pkgAcquireStatus2() : pkgAcquireStatus() {} +pkgAcquireStatus2::~pkgAcquireStatus2() {} + + +pkgAcquire::UriIterator::UriIterator(pkgAcquire::Queue *Q) : d(NULL), CurQ(Q), CurItem(0) +{ + while (CurItem == 0 && CurQ != 0) + { + CurItem = CurQ->Items; + CurQ = CurQ->Next; + } +} + +pkgAcquire::UriIterator::~UriIterator() {} +pkgAcquire::MethodConfig::~MethodConfig() {} +pkgAcquireStatus::~pkgAcquireStatus() {} diff --git a/apt-pkg/acquire.h b/apt-pkg/acquire.h new file mode 100644 index 0000000..1cf4da5 --- /dev/null +++ b/apt-pkg/acquire.h @@ -0,0 +1,886 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Acquire - File Acquiration + + This module contains the Acquire system. It is responsible for bringing + files into the local pathname space. It deals with URIs for files and + URI handlers responsible for downloading or finding the URIs. + + Each file to download is represented by an Acquire::Item class subclassed + into a specialization. The Item class can add itself to several URI + acquire queues each prioritized by the download scheduler. When the + system is run the proper URI handlers are spawned and the acquire + queues are fed into the handlers by the schedular until the queues are + empty. This allows for an Item to be downloaded from an alternate source + if the first try turns out to fail. It also allows concurrent downloading + of multiple items from multiple sources as well as dynamic balancing + of load between the sources. + + Schedualing of downloads is done on a first ask first get basis. This + preserves the order of the download as much as possible. And means the + fastest source will tend to process the largest number of files. + + Internal methods and queues for performing gzip decompression, + md5sum hashing and file copying are provided to allow items to apply + a number of transformations to the data files they are working with. + + ##################################################################### */ + /*}}}*/ + +/** \defgroup acquire Acquire system {{{ + * + * \brief The Acquire system is responsible for retrieving files from + * local or remote URIs and postprocessing them (for instance, + * verifying their authenticity). The core class in this system is + * pkgAcquire, which is responsible for managing the download queues + * during the download. There is at least one download queue for + * each supported protocol; protocols such as http may provide one + * queue per host. + * + * Each file to download is represented by a subclass of + * pkgAcquire::Item. The files add themselves to the download + * queue(s) by providing their URI information to + * pkgAcquire::Item::QueueURI, which calls pkgAcquire::Enqueue. + * + * Once the system is set up, the Run method will spawn subprocesses + * to handle the enqueued URIs; the scheduler will then take items + * from the queues and feed them into the handlers until the queues + * are empty. + * + * \todo Acquire supports inserting an object into several queues at + * once, but it is not clear what its behavior in this case is, and + * no subclass of pkgAcquire::Item seems to actually use this + * capability. + */ /*}}}*/ + +/** \addtogroup acquire + * + * @{ + * + * \file acquire.h + */ + +#ifndef PKGLIB_ACQUIRE_H +#define PKGLIB_ACQUIRE_H + +#include <apt-pkg/hashes.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/weakptr.h> + +#include <string> +#include <vector> + +#include <stddef.h> +#include <sys/select.h> +#include <sys/time.h> + +#ifndef APT_10_CLEANER_HEADERS +#include <unistd.h> +#endif + +#ifndef APT_8_CLEANER_HEADERS +using std::vector; +using std::string; +#endif + +class pkgAcquireStatus; +class metaIndex; + +/** \brief The core download scheduler. {{{ + * + * This class represents an ongoing download. It manages the lists + * of active and pending downloads and handles setting up and tearing + * down download-related structures. + * + * \todo Why all the protected data items and methods? + */ +class pkgAcquire +{ + private: + /** \brief FD of the Lock file we acquire in Setup (if any) */ + int LockFD; + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + public: + + class Item; + class Queue; + class Worker; + struct MethodConfig; + struct ItemDesc; + friend class Item; + friend class pkgAcqMetaBase; + friend class Queue; + + typedef std::vector<Item *>::iterator ItemIterator; + typedef std::vector<Item *>::const_iterator ItemCIterator; + + protected: + + /** \brief A list of items to download. + * + * This is built monotonically as items are created and only + * emptied when the download shuts down. + */ + std::vector<Item *> Items; + + /** \brief The head of the list of active queues. + * + * \todo why a hand-managed list of queues instead of std::list or + * std::set? + */ + Queue *Queues; + + /** \brief The head of the list of active workers. + * + * \todo why a hand-managed list of workers instead of std::list + * or std::set? + */ + Worker *Workers; + + /** \brief The head of the list of acquire method configurations. + * + * Each protocol (http, ftp, gzip, etc) via which files can be + * fetched can have a representation in this list. The + * configuration data is filled in by parsing the 100 Capabilities + * string output by a method on startup (see + * pkgAcqMethod::pkgAcqMethod and pkgAcquire::GetConfig). + * + * \todo why a hand-managed config dictionary instead of std::map? + */ + MethodConfig *Configs; + + /** \brief The progress indicator for this download. */ + pkgAcquireStatus *Log; + + /** \brief The number of files which are to be fetched. */ + unsigned long ToFetch; + + // Configurable parameters for the scheduler + + /** \brief Represents the queuing strategy for remote URIs. */ + enum QueueStrategy { + /** \brief Generate one queue for each protocol/host combination; downloads from + * multiple hosts can proceed in parallel. + */ + QueueHost, + /** \brief Generate a single queue for each protocol; serialize + * downloads from multiple hosts. + */ + QueueAccess} QueueMode; + + /** \brief If \b true, debugging information will be dumped to std::clog. */ + bool const Debug; + /** \brief If \b true, a download is currently in progress. */ + bool Running; + + /** \brief Add the given item to the list of items. */ + void Add(Item *Item); + + /** \brief Remove the given item from the list of items. */ + void Remove(Item *Item); + + /** \brief Add the given worker to the list of workers. */ + void Add(Worker *Work); + + /** \brief Remove the given worker from the list of workers. */ + void Remove(Worker *Work); + + /** \brief Insert the given fetch request into the appropriate queue. + * + * \param Item The URI to download and the item to download it + * for. Copied by value into the queue; no reference to Item is + * retained. + */ + void Enqueue(ItemDesc &Item); + + /** \brief Remove all fetch requests for this item from all queues. */ + void Dequeue(Item *Item); + + /** \brief Determine the fetch method and queue of a URI. + * + * \param URI The URI to fetch. + * + * \param[out] Config A location in which to place the method via + * which the URI is to be fetched. + * + * \return the string-name of the queue in which a fetch request + * for the given URI should be placed. + */ + std::string QueueName(std::string URI,MethodConfig const *&Config); + + /** \brief Build up the set of file descriptors upon which select() should + * block. + * + * The default implementation inserts the file descriptors + * corresponding to active downloads. + * + * \param[out] Fd The largest file descriptor in the generated sets. + * + * \param[out] RSet The set of file descriptors that should be + * watched for input. + * + * \param[out] WSet The set of file descriptors that should be + * watched for output. + */ + virtual void SetFds(int &Fd,fd_set *RSet,fd_set *WSet); + + /** Handle input from and output to file descriptors which select() + * has determined are ready. The default implementation + * dispatches to all active downloads. + * + * \param RSet The set of file descriptors that are ready for + * input. + * + * \param WSet The set of file descriptors that are ready for + * output. + * + * \return false if there is an error condition on one of the fds + */ + bool RunFdsSane(fd_set *RSet,fd_set *WSet); + + // just here for compatibility, needs to be removed on the next + // ABI/API break. RunFdsSane() is what should be used as it + // returns if there is an error condition on one of the fds + virtual void RunFds(fd_set *RSet,fd_set *WSet); + + /** \brief Check for idle queues with ready-to-fetch items. + * + * Called by pkgAcquire::Queue::Done each time an item is dequeued + * but remains on some queues; i.e., another queue should start + * fetching it. + */ + void Bump(); + + public: + + /** \brief Retrieve information about a fetch method by name. + * + * \param Access The name of the method to look up. + * + * \return the method whose name is Access, or \b NULL if no such method exists. + */ + MethodConfig *GetConfig(std::string Access); + + /** \brief Provides information on how a download terminated. */ + enum RunResult { + /** \brief All files were fetched successfully. */ + Continue, + + /** \brief Some files failed to download. */ + Failed, + + /** \brief The download was cancelled by the user (i.e., #Log's + * pkgAcquireStatus::Pulse() method returned \b false). + */ + Cancelled}; + + /** \brief Download all the items that have been Add()ed to this + * download process. + * + * This method will block until the download completes, invoking + * methods on #Log to report on the progress of the download. + * + * \param PulseInterval The method pkgAcquireStatus::Pulse will be + * invoked on #Log at intervals of PulseInterval milliseconds. + * + * \return the result of the download. + */ + RunResult Run(int PulseInterval=500000); + + /** \brief Remove all items from this download process, terminate + * all download workers, and empty all queues. + */ + void Shutdown(); + + /** \brief Get the first Worker object. + * + * \return the first active worker in this download process. + */ + inline Worker *WorkersBegin() {return Workers;}; + + /** \brief Advance to the next Worker object. + * + * \return the worker immediately following I, or \b NULL if none + * exists. + */ + Worker *WorkerStep(Worker *I) APT_PURE; + + /** \brief Get the head of the list of items. */ + inline ItemIterator ItemsBegin() {return Items.begin();}; + inline ItemCIterator ItemsBegin() const {return Items.begin();}; + + /** \brief Get the end iterator of the list of items. */ + inline ItemIterator ItemsEnd() {return Items.end();}; + inline ItemCIterator ItemsEnd() const {return Items.end();}; + + // Iterate over queued Item URIs + class UriIterator; + /** \brief Get the head of the list of enqueued item URIs. + * + * This iterator will step over every element of every active + * queue. + */ + UriIterator UriBegin(); + /** \brief Get the end iterator of the list of enqueued item URIs. */ + UriIterator UriEnd(); + + /** Deletes each entry in the given directory that is not being + * downloaded by this object. For instance, when downloading new + * list files, calling Clean() will delete the old ones. + * + * \param Dir The directory to be cleaned out. + * + * \return \b true if the directory exists and is readable. + */ + bool Clean(std::string Dir); + + /** \return the total size in bytes of all the items included in + * this download. + */ + unsigned long long TotalNeeded(); + + /** \return the size in bytes of all non-local items included in + * this download. + */ + unsigned long long FetchNeeded(); + + /** \return the amount of data to be fetched that is already + * present on the filesystem. + */ + unsigned long long PartialPresent(); + + /** \brief Delayed constructor + * + * \param Progress indicator associated with this download or + * \b NULL for none. This object is not owned by the + * download process and will not be deleted when the pkgAcquire + * object is destroyed. Naturally, it should live for at least as + * long as the pkgAcquire object does. + * \param Lock defines a lock file that should be acquired to ensure + * only one Acquire class is in action at the time or an empty string + * if no lock file should be used. If set also all needed directories + * will be created. + */ + APT_DEPRECATED_MSG("Use constructors, .SetLog and .GetLock as needed") bool Setup(pkgAcquireStatus *Progress = NULL, std::string const &Lock = ""); + + void SetLog(pkgAcquireStatus *Progress) { Log = Progress; } + + /** \brief acquire lock and perform directory setup + * + * \param Lock defines a lock file that should be acquired to ensure + * only one Acquire class is in action at the time or an empty string + * if no lock file should be used. If set also all needed directories + * will be created and setup. + */ + bool GetLock(std::string const &Lock); + + /** \brief Construct a new pkgAcquire. */ + explicit pkgAcquire(pkgAcquireStatus *Log); + pkgAcquire(); + + /** \brief Destroy this pkgAcquire object. + * + * Destroys all queue, method, and item objects associated with + * this download. + */ + virtual ~pkgAcquire(); + + private: + APT_HIDDEN void Initialize(); +}; + +/** \brief Represents a single download source from which an item + * should be downloaded. + * + * An item may have several associated ItemDescs over its lifetime. + */ +struct pkgAcquire::ItemDesc : public WeakPointable +{ + /** \brief URI from which to download this item. */ + std::string URI; + /** \brief description of this item. */ + std::string Description; + /** \brief shorter description of this item. */ + std::string ShortDesc; + /** \brief underlying item which is to be downloaded. */ + Item *Owner; +}; + /*}}}*/ +/** \brief A single download queue in a pkgAcquire object. {{{ + * + * \todo Why so many protected values? + */ +class pkgAcquire::Queue +{ + friend class pkgAcquire; + friend class pkgAcquire::UriIterator; + friend class pkgAcquire::Worker; + + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + /** \brief The next queue in the pkgAcquire object's list of queues. */ + Queue *Next; + + protected: + + /** \brief A single item placed in this queue. */ + struct QItem : public ItemDesc + { + /** \brief The next item in the queue. */ + QItem *Next; + /** \brief The worker associated with this item, if any. */ + pkgAcquire::Worker *Worker; + + /** \brief The underlying items interested in the download */ + std::vector<Item*> Owners; + + typedef std::vector<Item*>::const_iterator owner_iterator; + + /** \brief Assign the ItemDesc portion of this QItem from + * another ItemDesc + */ + void operator =(pkgAcquire::ItemDesc const &I) + { + URI = I.URI; + Description = I.Description; + ShortDesc = I.ShortDesc; + Owners.clear(); + Owners.push_back(I.Owner); + Owner = I.Owner; + }; + + /** @return the sum of all expected hashes by all owners */ + HashStringList GetExpectedHashes() const; + + /** @return smallest maximum size of all owners */ + unsigned long long GetMaximumSize() const; + + /** \brief get partial files in order */ + void SyncDestinationFiles() const; + + /** @return the custom headers to use for this item */ + std::string Custom600Headers() const; + /** @return the maximum priority of this item */ + int APT_HIDDEN GetPriority() const; + }; + + /** \brief The name of this queue. */ + std::string Name; + + /** \brief The head of the list of items contained in this queue. + * + * \todo why a by-hand list instead of an STL structure? + */ + QItem *Items; + + /** \brief The head of the list of workers associated with this queue. + * + * \todo This is plural because support exists in Queue for + * multiple workers. However, it does not appear that there is + * any way to actually associate more than one worker with a + * queue. + * + * \todo Why not just use a std::set? + */ + pkgAcquire::Worker *Workers; + + /** \brief the download scheduler with which this queue is associated. */ + pkgAcquire *Owner; + + /** \brief The number of entries in this queue that are currently + * being downloaded. + */ + signed long PipeDepth; + + /** \brief The maximum number of entries that this queue will + * attempt to download at once. + */ + unsigned long MaxPipeDepth; + + public: + + /** \brief Insert the given fetch request into this queue. + * + * \return \b true if the queuing was successful. May return + * \b false if the Item is already in the queue + */ + bool Enqueue(ItemDesc &Item); + + /** \brief Remove all fetch requests for the given item from this queue. + * + * \return \b true if at least one request was removed from the queue. + */ + bool Dequeue(Item *Owner); + + /** \brief Locate an item in this queue. + * + * \param URI A URI to match against. + * \param Owner A pkgAcquire::Worker to match against. + * + * \return the first item in the queue whose URI is #URI and that + * is being downloaded by #Owner. + */ + QItem *FindItem(std::string URI,pkgAcquire::Worker *Owner) APT_PURE; + + /** Presumably this should start downloading an item? + * + * \todo Unimplemented. Implement it or remove? + */ + bool ItemStart(QItem *Itm,unsigned long long Size); + + /** \brief Remove the given item from this queue and set its state + * to pkgAcquire::Item::StatDone. + * + * If this is the only queue containing the item, the item is also + * removed from the main queue by calling pkgAcquire::Dequeue. + * + * \param Itm The item to remove. + * + * \return \b true if no errors are encountered. + */ + bool ItemDone(QItem *Itm); + + /** \brief Start the worker process associated with this queue. + * + * If a worker process is already associated with this queue, + * this is equivalent to calling Cycle(). + * + * \return \b true if the startup was successful. + */ + bool Startup(); + + /** \brief Shut down the worker process associated with this queue. + * + * \param Final If \b true, then the process is stopped unconditionally. + * Otherwise, it is only stopped if it does not need cleanup + * as indicated by the pkgAcqMethod::NeedsCleanup member of + * its configuration. + * + * \return \b true. + */ + bool Shutdown(bool Final); + + /** \brief Send idle items to the worker process. + * + * Fills up the pipeline by inserting idle items into the worker's queue. + */ + bool Cycle(); + + /** \brief Check for items that could be enqueued. + * + * Call this after an item placed in multiple queues has gone from + * the pkgAcquire::Item::StatFetching state to the + * pkgAcquire::Item::StatIdle state, to possibly refill an empty queue. + * This is an alias for Cycle(). + * + * \todo Why both this and Cycle()? Are they expected to be + * different someday? + */ + void Bump(); + + /** \brief Create a new Queue. + * + * \param Name The name of the new queue. + * \param Owner The download process that owns the new queue. + */ + Queue(std::string const &Name,pkgAcquire * const Owner); + + /** Shut down all the worker processes associated with this queue + * and empty the queue. + */ + virtual ~Queue(); +}; + /*}}}*/ +/** \brief Iterates over all the URIs being fetched by a pkgAcquire object. {{{*/ +class pkgAcquire::UriIterator +{ + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + /** The next queue to iterate over. */ + pkgAcquire::Queue *CurQ; + /** The item that we currently point at. */ + pkgAcquire::Queue::QItem *CurItem; + + public: + + inline void operator ++() {operator ++(0);}; + + void operator ++(int) + { + CurItem = CurItem->Next; + while (CurItem == 0 && CurQ != 0) + { + CurItem = CurQ->Items; + CurQ = CurQ->Next; + } + }; + + inline pkgAcquire::Queue::QItem const *operator ->() const {return CurItem;}; + inline bool operator !=(UriIterator const &rhs) const {return rhs.CurQ != CurQ || rhs.CurItem != CurItem;}; + inline bool operator ==(UriIterator const &rhs) const {return rhs.CurQ == CurQ && rhs.CurItem == CurItem;}; + + /** \brief Create a new UriIterator. + * + * \param Q The queue over which this UriIterator should iterate. + */ + explicit UriIterator(pkgAcquire::Queue *Q); + virtual ~UriIterator(); +}; + /*}}}*/ +/** \brief Information about the properties of a single acquire method. {{{*/ +struct pkgAcquire::MethodConfig +{ + class Private; + /** \brief dpointer placeholder (for later in case we need it) */ + Private *const d; + + /** \brief The next link on the acquire method list. + * + * \todo Why not an STL container? + */ + MethodConfig *Next; + + /** \brief The name of this acquire method (e.g., http). */ + std::string Access; + + /** \brief The implementation version of this acquire method. */ + std::string Version; + + /** \brief If \b true, only one download queue should be created for this + * method. + */ + bool SingleInstance; + + /** \brief If \b true, this method supports pipelined downloading. */ + bool Pipeline; + + /** \brief If \b true, the worker process should send the entire + * APT configuration tree to the fetch subprocess when it starts + * up. + */ + bool SendConfig; + + /** \brief If \b true, this fetch method does not require network access; + * all files are to be acquired from the local disk. + */ + bool LocalOnly; + + /** \brief If \b true, the subprocess has to carry out some cleanup + * actions before shutting down. + * + * For instance, the cdrom method needs to unmount the CD after it + * finishes. + */ + bool NeedsCleanup; + + /** \brief If \b true, this fetch method acquires files from removable media. */ + bool Removable; + + /** \brief Set up the default method parameters. + * + * All fields are initialized to NULL, "", or \b false as + * appropriate. + */ + MethodConfig(); + + APT_HIDDEN bool GetAuxRequests() const; + APT_HIDDEN void SetAuxRequests(bool const value); + + virtual ~MethodConfig(); +}; + /*}}}*/ +/** \brief A monitor object for downloads controlled by the pkgAcquire class. {{{ + * + * \todo Why protected members? + */ +class pkgAcquireStatus +{ + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + protected: + + /** \brief The last time at which this monitor object was updated. */ + struct timeval Time; + + /** \brief The time at which the download started. */ + struct timeval StartTime; + + /** \brief The number of bytes fetched as of the previous call to + * pkgAcquireStatus::Pulse, including local items. + */ + unsigned long long LastBytes; + + /** \brief The current rate of download as of the most recent call + * to pkgAcquireStatus::Pulse, in bytes per second. + */ + unsigned long long CurrentCPS; + + /** \brief The number of bytes fetched as of the most recent call + * to pkgAcquireStatus::Pulse, including local items. + */ + unsigned long long CurrentBytes; + + /** \brief The total number of bytes that need to be fetched. + * + * \warning This member is inaccurate, as new items might be + * enqueued while the download is in progress! + */ + unsigned long long TotalBytes; + + /** \brief The total number of bytes accounted for by items that + * were successfully fetched. + */ + unsigned long long FetchedBytes; + + /** \brief The amount of time that has elapsed since the download + * started. + */ + unsigned long long ElapsedTime; + + /** \brief The total number of items that need to be fetched. + * + * \warning This member is inaccurate, as new items might be + * enqueued while the download is in progress! + */ + unsigned long TotalItems; + + /** \brief The number of items that have been successfully downloaded. */ + unsigned long CurrentItems; + + /** \brief The estimated percentage of the download (0-100) + */ + double Percent; + + public: + + /** \brief If \b true, the download scheduler should call Pulse() + * at the next available opportunity. + */ + bool Update; + + /** \brief If \b true, extra Pulse() invocations will be performed. + * + * With this option set, Pulse() will be called every time that a + * download item starts downloading, finishes downloading, or + * terminates with an error. + */ + bool MorePulses; + + /** \brief Invoked when a local or remote file has been completely fetched. + * + * \param Size The size of the file fetched. + * + * \param ResumePoint How much of the file was already fetched. + */ + virtual void Fetched(unsigned long long Size,unsigned long long ResumePoint); + + /** \brief Invoked when the user should be prompted to change the + * inserted removable media. + * + * This method should not return until the user has confirmed to + * the user interface that the media change is complete. + * + * \param Media The name of the media type that should be changed. + * + * \param Drive The identifying name of the drive whose media + * should be changed. + * + * \return \b true if the user confirms the media change, \b + * false if it is cancelled. + * + * \todo This is a horrible blocking monster; it should be CPSed + * with prejudice. + */ + virtual bool MediaChange(std::string Media,std::string Drive) = 0; + + struct ReleaseInfoChange + { + std::string Type; /*!< Type of the change like "Origin", "Codename", "Version", … */ + std::string From; /*!< old value */ + std::string To; /*!< new value */ + std::string Message; /*!< translated message describing the change */ + bool DefaultAction; /*!< true if the change is informational, false if it must be explicitly confirmed */ + }; + /** \brief ask the user for confirmation of changes to infos about a repository + * + * This method should present the user with a choice of accepting the change + * or not and indicate the user opinion via the return value. If DefaultAction is true + * it is acceptable to only notify the user about the change, but to accept the change + * automatically on behalf of the user. + * + * The default implementation will fail if any Change has DefaultAction == false. Regardless of + * success it will print for each change the message attached to it via GlobalError either as an + * error (if DefaultAction == false) or as a notice otherwise. + * + * \b Note: To keep ABI compatibility for now this method isn't marked as + * virtual, but you can derive your class from #pkgAcquireStatus2 which has it + * marked as virtual. TODO on next ABI break: merge both classes. + * + * @param LastRelease can be used to extract further information from the previous Release file + * @param CurrentRelease can be used to extract further information from the current Release file + * @param Changes is an array of changes alongside explanatory messages + * which should be presented in some way to the user. + * @return \b true if all changes are accepted by user, otherwise or if user can't be asked \b false + */ + bool ReleaseInfoChanges(metaIndex const * const LastRelease, metaIndex const * const CurrentRelease, std::vector<ReleaseInfoChange> &&Changes); + APT_HIDDEN static bool ReleaseInfoChangesAsGlobalErrors(std::vector<ReleaseInfoChange> &&Changes); + + /** \brief Invoked when an item is confirmed to be up-to-date. + + * For instance, when an HTTP download is informed that the file on + * the server was not modified. + */ + virtual void IMSHit(pkgAcquire::ItemDesc &/*Itm*/) {}; + + /** \brief Invoked when some of an item's data is fetched. */ + virtual void Fetch(pkgAcquire::ItemDesc &/*Itm*/) {}; + + /** \brief Invoked when an item is successfully and completely fetched. */ + virtual void Done(pkgAcquire::ItemDesc &/*Itm*/) {}; + + /** \brief Invoked when the process of fetching an item encounters + * a fatal error. + */ + virtual void Fail(pkgAcquire::ItemDesc &/*Itm*/) {}; + + /** \brief Periodically invoked while the Acquire process is underway. + * + * Subclasses should first call pkgAcquireStatus::Pulse(), then + * update their status output. The download process is blocked + * while Pulse() is being called. + * + * \return \b false if the user asked to cancel the whole Acquire process. + * + * \see pkgAcquire::Run + */ + virtual bool Pulse(pkgAcquire *Owner); + + /** \brief Invoked when the Acquire process starts running. */ + virtual void Start(); + + /** \brief Invoked when the Acquire process stops running. */ + virtual void Stop(); + + /** \brief Initialize all counters to 0 and the time to the current time. */ + pkgAcquireStatus(); + virtual ~pkgAcquireStatus(); +}; +class pkgAcquireStatus2: public pkgAcquireStatus +{ +public: + virtual bool ReleaseInfoChanges(metaIndex const * const LastRelease, metaIndex const * const CurrentRelease, std::vector<ReleaseInfoChange> &&Changes); + + pkgAcquireStatus2(); + virtual ~pkgAcquireStatus2(); +}; + /*}}}*/ +/** @} */ + +#endif diff --git a/apt-pkg/algorithms.cc b/apt-pkg/algorithms.cc new file mode 100644 index 0000000..bb0e2f8 --- /dev/null +++ b/apt-pkg/algorithms.cc @@ -0,0 +1,1418 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Algorithms - A set of misc algorithms + + The pkgProblemResolver class has become insanely complex and + very sophisticated, it handles every test case I have thrown at it + to my satisfaction. Understanding exactly why all the steps the class + does are required is difficult and changing though not very risky + may result in other cases not working. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/algorithms.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/dpkgpm.h> +#include <apt-pkg/edsp.h> +#include <apt-pkg/error.h> +#include <apt-pkg/packagemanager.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/prettyprinters.h> + +#include <cstdlib> +#include <iostream> +#include <string> +#include <utility> +#include <string.h> + +#include <apti18n.h> + /*}}}*/ +using namespace std; + +class APT_HIDDEN pkgSimulatePrivate +{ +public: + std::vector<pkgDPkgPM::Item> List; +}; +// Simulate::Simulate - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* The legacy translations here of input Pkg iterators is obsolete, + this is not necessary since the pkgCaches are fully shared now. */ +pkgSimulate::pkgSimulate(pkgDepCache *Cache) : pkgPackageManager(Cache), + d(new pkgSimulatePrivate()), iPolicy(Cache), + Sim(&Cache->GetCache(),&iPolicy), + group(Sim) +{ + Sim.Init(0); + auto PackageCount = Cache->Head().PackageCount; + Flags = new unsigned char[PackageCount]; + memset(Flags,0,sizeof(*Flags)*PackageCount); + + // Fake a filename so as not to activate the media swapping + string Jnk = "SIMULATE"; + for (decltype(PackageCount) I = 0; I != PackageCount; ++I) + FileNames[I] = Jnk; +} + /*}}}*/ +// Simulate::~Simulate - Destructor /*{{{*/ +pkgSimulate::~pkgSimulate() +{ + delete[] Flags; + delete d; +} + /*}}}*/ +// Simulate::Describe - Describe a package /*{{{*/ +// --------------------------------------------------------------------- +/* Parameter Current == true displays the current package version, + Parameter Candidate == true displays the candidate package version */ +void pkgSimulate::Describe(PkgIterator Pkg,ostream &out,bool Current,bool Candidate) +{ + VerIterator Ver(Sim); + + out << Pkg.FullName(true); + + if (Current == true) + { + Ver = Pkg.CurrentVer(); + if (Ver.end() == false) + out << " [" << Ver.VerStr() << ']'; + } + + if (Candidate == true) + { + Ver = Sim[Pkg].CandidateVerIter(Sim); + if (Ver.end() == true) + return; + + out << " (" << Ver.VerStr() << ' ' << Ver.RelStr() << ')'; + } +} + /*}}}*/ +// Simulate::Install - Simulate unpacking of a package /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgSimulate::Install(PkgIterator iPkg,string File) +{ + if (iPkg.end() || File.empty()) + return false; + d->List.emplace_back(pkgDPkgPM::Item::Install, iPkg, File); + return true; +} +bool pkgSimulate::RealInstall(PkgIterator iPkg,string /*File*/) +{ + // Adapt the iterator + PkgIterator Pkg = Sim.FindPkg(iPkg.Name(), iPkg.Arch()); + Flags[Pkg->ID] = 1; + + cout << "Inst "; + Describe(Pkg,cout,true,true); + Sim.MarkInstall(Pkg,false); + + // Look for broken conflicts+predepends. + for (PkgIterator I = Sim.PkgBegin(); I.end() == false; ++I) + { + if (Sim[I].InstallVer == 0) + continue; + + for (DepIterator D = Sim[I].InstVerIter(Sim).DependsList(); D.end() == false;) + { + DepIterator Start; + DepIterator End; + D.GlobOr(Start,End); + if (Start.IsNegative() == true || + End->Type == pkgCache::Dep::PreDepends) + { + if ((Sim[End] & pkgDepCache::DepGInstall) == 0) + { + cout << " [" << I.FullName(false) << " on " << Start.TargetPkg().FullName(false) << ']'; + if (Start->Type == pkgCache::Dep::Conflicts) + _error->Error("Fatal, conflicts violated %s",I.FullName(false).c_str()); + } + } + } + } + + if (Sim.BrokenCount() != 0) + ShortBreaks(); + else + cout << endl; + return true; +} + /*}}}*/ +// Simulate::Configure - Simulate configuration of a Package /*{{{*/ +// --------------------------------------------------------------------- +/* This is not an accurate simulation of relatity, we should really not + install the package.. For some investigations it may be necessary + however. */ +bool pkgSimulate::Configure(PkgIterator iPkg) +{ + if (iPkg.end()) + return false; + d->List.emplace_back(pkgDPkgPM::Item::Configure, iPkg); + return true; +} +bool pkgSimulate::RealConfigure(PkgIterator iPkg) +{ + // Adapt the iterator + PkgIterator Pkg = Sim.FindPkg(iPkg.Name(), iPkg.Arch()); + + Flags[Pkg->ID] = 2; + + if (Sim[Pkg].InstBroken() == true) + { + cout << "Conf " << Pkg.FullName(false) << " broken" << endl; + + Sim.Update(); + + // Print out each package and the failed dependencies + for (pkgCache::DepIterator D = Sim[Pkg].InstVerIter(Sim).DependsList(); D.end() == false; ++D) + { + if (Sim.IsImportantDep(D) == false || + (Sim[D] & pkgDepCache::DepInstall) != 0) + continue; + + if (D->Type == pkgCache::Dep::Obsoletes) + cout << " Obsoletes:" << D.TargetPkg().FullName(false); + else if (D->Type == pkgCache::Dep::Conflicts) + cout << " Conflicts:" << D.TargetPkg().FullName(false); + else if (D->Type == pkgCache::Dep::DpkgBreaks) + cout << " Breaks:" << D.TargetPkg().FullName(false); + else + cout << " Depends:" << D.TargetPkg().FullName(false); + } + cout << endl; + + _error->Error("Conf Broken %s",Pkg.FullName(false).c_str()); + } + else + { + cout << "Conf "; + Describe(Pkg,cout,false,true); + } + + if (Sim.BrokenCount() != 0) + ShortBreaks(); + else + cout << endl; + + return true; +} + /*}}}*/ +// Simulate::Remove - Simulate the removal of a package /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgSimulate::Remove(PkgIterator iPkg,bool Purge) +{ + if (iPkg.end()) + return false; + d->List.emplace_back(Purge ? pkgDPkgPM::Item::Purge : pkgDPkgPM::Item::Remove, iPkg); + return true; +} +bool pkgSimulate::RealRemove(PkgIterator iPkg,bool Purge) +{ + // Adapt the iterator + PkgIterator Pkg = Sim.FindPkg(iPkg.Name(), iPkg.Arch()); + if (Pkg.end() == true) + { + std::cerr << (Purge ? "Purg" : "Remv") << " invalid package " << iPkg.FullName() << std::endl; + return false; + } + + Flags[Pkg->ID] = 3; + Sim.MarkDelete(Pkg); + + if (Purge == true) + cout << "Purg "; + else + cout << "Remv "; + Describe(Pkg,cout,true,false); + + if (Sim.BrokenCount() != 0) + ShortBreaks(); + else + cout << endl; + + return true; +} + /*}}}*/ +// Simulate::ShortBreaks - Print out a short line describing all breaks /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgSimulate::ShortBreaks() +{ + cout << " ["; + for (PkgIterator I = Sim.PkgBegin(); I.end() == false; ++I) + { + if (Sim[I].InstBroken() == true) + { + if (Flags[I->ID] == 0) + cout << I.FullName(false) << ' '; +/* else + cout << I.Name() << "! ";*/ + } + } + cout << ']' << endl; +} + /*}}}*/ +bool pkgSimulate::Go2(APT::Progress::PackageManager *) /*{{{*/ +{ + if (pkgDPkgPM::ExpandPendingCalls(d->List, Cache) == false) + return false; + for (auto && I : d->List) + switch (I.Op) + { + case pkgDPkgPM::Item::Install: + if (RealInstall(I.Pkg, I.File) == false) + return false; + break; + case pkgDPkgPM::Item::Configure: + if (RealConfigure(I.Pkg) == false) + return false; + break; + case pkgDPkgPM::Item::Remove: + if (RealRemove(I.Pkg, false) == false) + return false; + break; + case pkgDPkgPM::Item::Purge: + if (RealRemove(I.Pkg, true) == false) + return false; + break; + case pkgDPkgPM::Item::ConfigurePending: + case pkgDPkgPM::Item::TriggersPending: + case pkgDPkgPM::Item::RemovePending: + case pkgDPkgPM::Item::PurgePending: + return _error->Error("Internal error, simulation encountered unexpected pending item"); + } + return true; +} + /*}}}*/ +// ApplyStatus - Adjust for non-ok packages /*{{{*/ +// --------------------------------------------------------------------- +/* We attempt to change the state of the all packages that have failed + installation toward their real state. The ordering code will perform + the necessary calculations to deal with the problems. */ +bool pkgApplyStatus(pkgDepCache &Cache) +{ + pkgDepCache::ActionGroup group(Cache); + + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + if (I->VersionList == 0) + continue; + + // Only choice for a ReInstReq package is to reinstall + if (I->InstState == pkgCache::State::ReInstReq || + I->InstState == pkgCache::State::HoldReInstReq) + { + if (I->CurrentVer != 0 && I.CurrentVer().Downloadable() == true) + Cache.MarkKeep(I, false, false); + else + { + // Is this right? Will dpkg choke on an upgrade? + if (Cache[I].CandidateVer != 0 && + Cache[I].CandidateVerIter(Cache).Downloadable() == true) + Cache.MarkInstall(I, false, 0, false); + else + return _error->Error(_("The package %s needs to be reinstalled, " + "but I can't find an archive for it."),I.FullName(true).c_str()); + } + + continue; + } + + switch (I->CurrentState) + { + /* This means installation failed somehow - it does not need to be + re-unpacked (probably) */ + case pkgCache::State::UnPacked: + case pkgCache::State::HalfConfigured: + case pkgCache::State::TriggersAwaited: + case pkgCache::State::TriggersPending: + if ((I->CurrentVer != 0 && I.CurrentVer().Downloadable() == true) || + I.State() != pkgCache::PkgIterator::NeedsUnpack) + Cache.MarkKeep(I, false, false); + else + { + if (Cache[I].CandidateVer != 0 && + Cache[I].CandidateVerIter(Cache).Downloadable() == true) + Cache.MarkInstall(I, true, 0, false); + else + Cache.MarkDelete(I, false, 0, false); + } + break; + + // This means removal failed + case pkgCache::State::HalfInstalled: + Cache.MarkDelete(I, false, 0, false); + break; + + default: + if (I->InstState != pkgCache::State::Ok) + return _error->Error("The package %s is not ok and I " + "don't know how to fix it!",I.FullName(false).c_str()); + } + } + return true; +} + /*}}}*/ +// FixBroken - Fix broken packages /*{{{*/ +// --------------------------------------------------------------------- +/* This autoinstalls every broken package and then runs the problem resolver + on the result. */ +bool pkgFixBroken(pkgDepCache &Cache) +{ + pkgDepCache::ActionGroup group(Cache); + + // Auto upgrade all broken packages + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + if (Cache[I].NowBroken() == true) + Cache.MarkInstall(I, true, 0, false); + + /* Fix packages that are in a NeedArchive state but don't have a + downloadable install version */ + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + if (I.State() != pkgCache::PkgIterator::NeedsUnpack || + Cache[I].Delete() == true) + continue; + + if (Cache[I].InstVerIter(Cache).Downloadable() == false) + continue; + + Cache.MarkInstall(I, true, 0, false); + } + + pkgProblemResolver Fix(&Cache); + return Fix.Resolve(true); +} + /*}}}*/ +// ProblemResolver::pkgProblemResolver - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgProblemResolver::pkgProblemResolver(pkgDepCache *pCache) : d(NULL), Cache(*pCache) +{ + // Allocate memory + auto const Size = Cache.Head().PackageCount; + Scores = new int[Size]; + Flags = new unsigned char[Size]; + memset(Flags,0,sizeof(*Flags)*Size); + + // Set debug to true to see its decision logic + Debug = _config->FindB("Debug::pkgProblemResolver",false); +} + /*}}}*/ +// ProblemResolver::~pkgProblemResolver - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgProblemResolver::~pkgProblemResolver() +{ + delete [] Scores; + delete [] Flags; +} + /*}}}*/ +// ProblemResolver::ScoreSort - Sort the list by score /*{{{*/ +// --------------------------------------------------------------------- +/* */ +int pkgProblemResolver::ScoreSort(Package const *A,Package const *B) +{ + if (Scores[A->ID] > Scores[B->ID]) + return -1; + if (Scores[A->ID] < Scores[B->ID]) + return 1; + return 0; +} + /*}}}*/ +// ProblemResolver::MakeScores - Make the score table /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgProblemResolver::MakeScores() +{ + auto const Size = Cache.Head().PackageCount; + memset(Scores,0,sizeof(*Scores)*Size); + + // maps to pkgCache::State::VerPriority: + // Required Important Standard Optional Extra + int PrioMap[] = { + 0, + _config->FindI("pkgProblemResolver::Scores::Required",3), + _config->FindI("pkgProblemResolver::Scores::Important",2), + _config->FindI("pkgProblemResolver::Scores::Standard",1), + _config->FindI("pkgProblemResolver::Scores::Optional",-1), + _config->FindI("pkgProblemResolver::Scores::Extra",-2) + }; + int PrioEssentials = _config->FindI("pkgProblemResolver::Scores::Essentials",100); + int PrioInstalledAndNotObsolete = _config->FindI("pkgProblemResolver::Scores::NotObsolete",1); + int DepMap[] = { + 0, + _config->FindI("pkgProblemResolver::Scores::Depends",1), + _config->FindI("pkgProblemResolver::Scores::PreDepends",1), + _config->FindI("pkgProblemResolver::Scores::Suggests",0), + _config->FindI("pkgProblemResolver::Scores::Recommends",1), + _config->FindI("pkgProblemResolver::Scores::Conflicts",-1), + _config->FindI("pkgProblemResolver::Scores::Replaces",0), + _config->FindI("pkgProblemResolver::Scores::Obsoletes",0), + _config->FindI("pkgProblemResolver::Scores::Breaks",-1), + _config->FindI("pkgProblemResolver::Scores::Enhances",0) + }; + int AddProtected = _config->FindI("pkgProblemResolver::Scores::AddProtected",10000); + int AddEssential = _config->FindI("pkgProblemResolver::Scores::AddEssential",5000); + + if (_config->FindB("Debug::pkgProblemResolver::ShowScores",false) == true) + clog << "Settings used to calculate pkgProblemResolver::Scores::" << endl + << " Required => " << PrioMap[pkgCache::State::Required] << endl + << " Important => " << PrioMap[pkgCache::State::Important] << endl + << " Standard => " << PrioMap[pkgCache::State::Standard] << endl + << " Optional => " << PrioMap[pkgCache::State::Optional] << endl + << " Extra => " << PrioMap[pkgCache::State::Extra] << endl + << " Essentials => " << PrioEssentials << endl + << " InstalledAndNotObsolete => " << PrioInstalledAndNotObsolete << endl + << " Pre-Depends => " << DepMap[pkgCache::Dep::PreDepends] << endl + << " Depends => " << DepMap[pkgCache::Dep::Depends] << endl + << " Recommends => " << DepMap[pkgCache::Dep::Recommends] << endl + << " Suggests => " << DepMap[pkgCache::Dep::Suggests] << endl + << " Conflicts => " << DepMap[pkgCache::Dep::Conflicts] << endl + << " Breaks => " << DepMap[pkgCache::Dep::DpkgBreaks] << endl + << " Replaces => " << DepMap[pkgCache::Dep::Replaces] << endl + << " Obsoletes => " << DepMap[pkgCache::Dep::Obsoletes] << endl + << " Enhances => " << DepMap[pkgCache::Dep::Enhances] << endl + << " AddProtected => " << AddProtected << endl + << " AddEssential => " << AddEssential << endl; + + // Generate the base scores for a package based on its properties + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + if (Cache[I].InstallVer == 0) + continue; + + int &Score = Scores[I->ID]; + + /* This is arbitrary, it should be high enough to elevate an + essantial package above most other packages but low enough + to allow an obsolete essential packages to be removed by + a conflicts on a powerful normal package (ie libc6) */ + if ((I->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential + || (I->Flags & pkgCache::Flag::Important) == pkgCache::Flag::Important) + Score += PrioEssentials; + + pkgCache::VerIterator const InstVer = Cache[I].InstVerIter(Cache); + // We apply priorities only to downloadable packages, all others are prio:extra + // as an obsolete prio:standard package can't be that standard anymore… + if (InstVer->Priority <= pkgCache::State::Extra && InstVer.Downloadable() == true) + Score += PrioMap[InstVer->Priority]; + else + Score += PrioMap[pkgCache::State::Extra]; + + /* This helps to fix oddball problems with conflicting packages + on the same level. We enhance the score of installed packages + if those are not obsolete */ + if (I->CurrentVer != 0 && Cache[I].CandidateVer != 0 && Cache[I].CandidateVerIter(Cache).Downloadable()) + Score += PrioInstalledAndNotObsolete; + + // propagate score points along dependencies + for (pkgCache::DepIterator D = InstVer.DependsList(); D.end() == false; ++D) + { + if (DepMap[D->Type] == 0) + continue; + pkgCache::PkgIterator const T = D.TargetPkg(); + if (D->Version != 0) + { + pkgCache::VerIterator const IV = Cache[T].InstVerIter(Cache); + if (IV.end() == true || D.IsSatisfied(IV) == false) + continue; + } + Scores[T->ID] += DepMap[D->Type]; + } + } + + // Copy the scores to advoid additive looping + std::unique_ptr<int[]> OldScores(new int[Size]); + memcpy(OldScores.get(),Scores,sizeof(*Scores)*Size); + + /* Now we cause 1 level of dependency inheritance, that is we add the + score of the packages that depend on the target Package. This + fortifies high scoring packages */ + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + if (Cache[I].InstallVer == 0) + continue; + + for (pkgCache::DepIterator D = I.RevDependsList(); D.end() == false; ++D) + { + // Only do it for the install version + if ((pkgCache::Version *)D.ParentVer() != Cache[D.ParentPkg()].InstallVer || + (D->Type != pkgCache::Dep::Depends && + D->Type != pkgCache::Dep::PreDepends && + D->Type != pkgCache::Dep::Recommends)) + continue; + + // Do not propagate negative scores otherwise + // an extra (-2) package might score better than an optional (-1) + if (OldScores[D.ParentPkg()->ID] > 0) + Scores[I->ID] += OldScores[D.ParentPkg()->ID]; + } + } + + /* Now we propagate along provides. This makes the packages that + provide important packages extremely important */ + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + for (pkgCache::PrvIterator P = I.ProvidesList(); P.end() == false; ++P) + { + // Only do it once per package + if ((pkgCache::Version *)P.OwnerVer() != Cache[P.OwnerPkg()].InstallVer) + continue; + Scores[P.OwnerPkg()->ID] += abs(Scores[I->ID] - OldScores[I->ID]); + } + } + + /* Protected things are pushed really high up. This number should put them + ahead of everything */ + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + if ((Flags[I->ID] & Protected) != 0) + Scores[I->ID] += AddProtected; + if ((I->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential || + (I->Flags & pkgCache::Flag::Important) == pkgCache::Flag::Important) + Scores[I->ID] += AddEssential; + } +} + /*}}}*/ +// ProblemResolver::DoUpgrade - Attempt to upgrade this package /*{{{*/ +// --------------------------------------------------------------------- +/* This goes through and tries to reinstall packages to make this package + installable */ +bool pkgProblemResolver::DoUpgrade(pkgCache::PkgIterator Pkg) +{ + pkgDepCache::ActionGroup group(Cache); + + if ((Flags[Pkg->ID] & Upgradable) == 0 || Cache[Pkg].Upgradable() == false) + return false; + if ((Flags[Pkg->ID] & Protected) == Protected) + return false; + + Flags[Pkg->ID] &= ~Upgradable; + + bool WasKept = Cache[Pkg].Keep(); + Cache.MarkInstall(Pkg, false, 0, false); + + // This must be a virtual package or something like that. + if (Cache[Pkg].InstVerIter(Cache).end() == true) + return false; + + // Isolate the problem dependency + bool Fail = false; + for (pkgCache::DepIterator D = Cache[Pkg].InstVerIter(Cache).DependsList(); D.end() == false;) + { + // Compute a single dependency element (glob or) + pkgCache::DepIterator Start = D; + pkgCache::DepIterator End = D; + for (bool LastOR = true; D.end() == false && LastOR == true;) + { + LastOR = (D->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or; + ++D; + if (LastOR == true) + End = D; + } + + // We only worry about critical deps. + if (End.IsCritical() != true) + continue; + + // Iterate over all the members in the or group + while (1) + { + // Dep is ok now + if ((Cache[End] & pkgDepCache::DepGInstall) == pkgDepCache::DepGInstall) + break; + + // Do not change protected packages + PkgIterator P = Start.SmartTargetPkg(); + if ((Flags[P->ID] & Protected) == Protected) + { + if (Debug == true) + clog << " Reinst Failed because of protected " << P.FullName(false) << endl; + Fail = true; + } + else + { + // Upgrade the package if the candidate version will fix the problem. + if ((Cache[Start] & pkgDepCache::DepCVer) == pkgDepCache::DepCVer) + { + if (DoUpgrade(P) == false) + { + if (Debug == true) + clog << " Reinst Failed because of " << P.FullName(false) << endl; + Fail = true; + } + else + { + Fail = false; + break; + } + } + else + { + /* We let the algorithm deal with conflicts on its next iteration, + it is much smarter than us */ + if (Start.IsNegative() == true) + break; + + if (Debug == true) + clog << " Reinst Failed early because of " << Start.TargetPkg().FullName(false) << endl; + Fail = true; + } + } + + if (Start == End) + break; + ++Start; + } + if (Fail == true) + break; + } + + // Undo our operations - it might be smart to undo everything this did.. + if (Fail == true) + { + if (WasKept == true) + Cache.MarkKeep(Pkg, false, false); + else + Cache.MarkDelete(Pkg, false, 0, false); + return false; + } + + if (Debug == true) + clog << " Re-Instated " << Pkg.FullName(false) << endl; + return true; +} + /*}}}*/ +// ProblemResolver::Resolve - calls a resolver to fix the situation /*{{{*/ +bool pkgProblemResolver::Resolve(bool BrokenFix, OpProgress * const Progress) +{ + std::string const solver = _config->Find("APT::Solver", "internal"); + auto const ret = EDSP::ResolveExternal(solver.c_str(), Cache, 0, Progress); + if (solver != "internal") + return ret; + return ResolveInternal(BrokenFix); +} + /*}}}*/ +// ProblemResolver::ResolveInternal - Run the resolution pass /*{{{*/ +// --------------------------------------------------------------------- +/* This routines works by calculating a score for each package. The score + is derived by considering the package's priority and all reverse + dependents giving an integer that reflects the amount of breakage that + adjusting the package will inflict. + + It goes from highest score to lowest and corrects all of the breaks by + keeping or removing the dependent packages. If that fails then it removes + the package itself and goes on. The routine should be able to intelligently + go from any broken state to a fixed state. + + The BrokenFix flag enables a mode where the algorithm tries to + upgrade packages to advoid problems. */ +bool pkgProblemResolver::ResolveInternal(bool const BrokenFix) +{ + pkgDepCache::ActionGroup group(Cache); + + // Record which packages are marked for install + bool Again = false; + do + { + Again = false; + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + if (Cache[I].Install() == true) + Flags[I->ID] |= PreInstalled; + else + { + if (Cache[I].InstBroken() == true && BrokenFix == true) + { + Cache.MarkInstall(I, false, 0, false); + if (Cache[I].Install() == true) + Again = true; + } + + Flags[I->ID] &= ~PreInstalled; + } + Flags[I->ID] |= Upgradable; + } + } + while (Again == true); + + if (Debug == true) { + clog << "Starting pkgProblemResolver with broken count: " + << Cache.BrokenCount() << endl; + } + + MakeScores(); + + auto const Size = Cache.Head().PackageCount; + + /* We have to order the packages so that the broken fixing pass + operates from highest score to lowest. This prevents problems when + high score packages cause the removal of lower score packages that + would cause the removal of even lower score packages. */ + std::unique_ptr<pkgCache::Package *[]> PList(new pkgCache::Package *[Size]); + pkgCache::Package **PEnd = PList.get(); + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + *PEnd++ = I; + + std::sort(PList.get(), PEnd, [this](Package *a, Package *b) { return ScoreSort(a, b) < 0; }); + + if (_config->FindB("Debug::pkgProblemResolver::ShowScores",false) == true) + { + clog << "Show Scores" << endl; + for (pkgCache::Package **K = PList.get(); K != PEnd; K++) + if (Scores[(*K)->ID] != 0) + { + pkgCache::PkgIterator Pkg(Cache,*K); + clog << Scores[(*K)->ID] << ' ' << APT::PrettyPkg(&Cache, Pkg) << std::endl; + } + } + + if (Debug == true) { + clog << "Starting 2 pkgProblemResolver with broken count: " + << Cache.BrokenCount() << endl; + } + + /* Now consider all broken packages. For each broken package we either + remove the package or fix it's problem. We do this once, it should + not be possible for a loop to form (that is a < b < c and fixing b by + changing a breaks c) */ + bool Change = true; + bool const TryFixByInstall = _config->FindB("pkgProblemResolver::FixByInstall", true); + std::vector<PackageKill> KillList; + for (int Counter = 0; Counter != 10 && Change == true; Counter++) + { + Change = false; + for (pkgCache::Package **K = PList.get(); K != PEnd; K++) + { + pkgCache::PkgIterator I(Cache,*K); + + /* We attempt to install this and see if any breaks result, + this takes care of some strange cases */ + if (Cache[I].CandidateVer != Cache[I].InstallVer && + I->CurrentVer != 0 && Cache[I].InstallVer != 0 && + (Flags[I->ID] & PreInstalled) != 0 && + (Flags[I->ID] & Protected) == 0 && + (Flags[I->ID] & ReInstateTried) == 0) + { + if (Debug == true) + clog << " Try to Re-Instate (" << Counter << ") " << I.FullName(false) << endl; + auto const OldBreaks = Cache.BrokenCount(); + pkgCache::Version *OldVer = Cache[I].InstallVer; + Flags[I->ID] &= ReInstateTried; + + Cache.MarkInstall(I, false, 0, false); + if (Cache[I].InstBroken() == true || + OldBreaks < Cache.BrokenCount()) + { + if (OldVer == 0) + Cache.MarkDelete(I, false, 0, false); + else + Cache.MarkKeep(I, false, false); + } + else + if (Debug == true) + clog << "Re-Instated " << I.FullName(false) << " (" << OldBreaks << " vs " << Cache.BrokenCount() << ')' << endl; + } + + if (Cache[I].InstallVer == 0 || Cache[I].InstBroken() == false) + continue; + + if (Debug == true) + clog << "Investigating (" << Counter << ") " << APT::PrettyPkg(&Cache, I) << endl; + + // Isolate the problem dependency + bool InOr = false; + pkgCache::DepIterator Start; + pkgCache::DepIterator End; + size_t OldSize = 0; + + KillList.resize(0); + + enum {OrRemove,OrKeep} OrOp = OrRemove; + for (pkgCache::DepIterator D = Cache[I].InstVerIter(Cache).DependsList(); + D.end() == false || InOr == true;) + { + // Compute a single dependency element (glob or) + if (Start == End) + { + // Decide what to do + if (InOr == true && OldSize == KillList.size()) + { + if (OrOp == OrRemove) + { + if ((Flags[I->ID] & Protected) != Protected) + { + if (Debug == true) + clog << " Or group remove for " << I.FullName(false) << endl; + Cache.MarkDelete(I, false, 0, false); + Change = true; + } + } + else if (OrOp == OrKeep) + { + if (Debug == true) + clog << " Or group keep for " << I.FullName(false) << endl; + Cache.MarkKeep(I, false, false); + Change = true; + } + } + + /* We do an extra loop (as above) to finalize the or group + processing */ + InOr = false; + OrOp = OrRemove; + D.GlobOr(Start,End); + if (Start.end() == true) + break; + + // We only worry about critical deps. + if (End.IsCritical() != true) + continue; + + InOr = Start != End; + OldSize = KillList.size(); + } + else + { + ++Start; + // We only worry about critical deps. + if (Start.IsCritical() != true) + continue; + } + + // Dep is ok + if ((Cache[End] & pkgDepCache::DepGInstall) == pkgDepCache::DepGInstall) + { + InOr = false; + continue; + } + + if (Debug == true) + clog << "Broken " << APT::PrettyDep(&Cache, Start) << endl; + + /* Look across the version list. If there are no possible + targets then we keep the package and bail. This is necessary + if a package has a dep on another package that can't be found */ + std::unique_ptr<pkgCache::Version *[]> VList(Start.AllTargets()); + if (VList[0] == 0 && (Flags[I->ID] & Protected) != Protected && + Start.IsNegative() == false && + Cache[I].NowBroken() == false) + { + if (InOr == true) + { + /* No keep choice because the keep being OK could be the + result of another element in the OR group! */ + continue; + } + + Change = true; + Cache.MarkKeep(I, false, false); + break; + } + + bool Done = false; + for (pkgCache::Version **V = VList.get(); *V != 0; V++) + { + pkgCache::VerIterator Ver(Cache,*V); + pkgCache::PkgIterator Pkg = Ver.ParentPkg(); + + /* This is a conflicts, and the version we are looking + at is not the currently selected version of the + package, which means it is not necessary to + remove/keep */ + if (Cache[Pkg].InstallVer != Ver && Start.IsNegative() == true) + { + if (Debug) + clog << " Conflicts//Breaks against version " + << Ver.VerStr() << " for " << Pkg.Name() + << " but that is not InstVer, ignoring" + << endl; + continue; + } + + if (Debug == true) + clog << " Considering " << Pkg.FullName(false) << ' ' << Scores[Pkg->ID] << + " as a solution to " << I.FullName(false) << ' ' << Scores[I->ID] << endl; + + /* Try to fix the package under consideration rather than + fiddle with the VList package */ + if (Scores[I->ID] <= Scores[Pkg->ID] || + ((Cache[Start] & pkgDepCache::DepNow) == 0 && + End.IsNegative() == false)) + { + // Try a little harder to fix protected packages.. + if ((Flags[I->ID] & Protected) == Protected) + { + if (DoUpgrade(Pkg) == true) + { + if (Scores[Pkg->ID] > Scores[I->ID]) + Scores[Pkg->ID] = Scores[I->ID]; + break; + } + + continue; + } + + /* See if a keep will do, unless the package is protected, + then installing it will be necessary */ + bool Installed = Cache[I].Install(); + Cache.MarkKeep(I, false, false); + if (Cache[I].InstBroken() == false) + { + // Unwind operation will be keep now + if (OrOp == OrRemove) + OrOp = OrKeep; + + // Restore + if (InOr == true && Installed == true) + Cache.MarkInstall(I, false, 0, false); + + if (Debug == true) + clog << " Holding Back " << I.FullName(false) << " rather than change " << Start.TargetPkg().FullName(false) << endl; + } + else + { + if (BrokenFix == false || DoUpgrade(I) == false) + { + // Consider other options + if (InOr == false || Cache[I].Garbage == true) + { + if (Debug == true) + clog << " Removing " << I.FullName(false) << " rather than change " << Start.TargetPkg().FullName(false) << endl; + Cache.MarkDelete(I, false, 0, false); + if (Counter > 1 && Scores[Pkg->ID] > Scores[I->ID]) + Scores[I->ID] = Scores[Pkg->ID]; + } + else if (TryFixByInstall == true && + Start.TargetPkg()->CurrentVer == 0 && + Cache[Start.TargetPkg()].Delete() == false && + (Flags[Start.TargetPkg()->ID] & ToRemove) != ToRemove && + Cache.GetCandidateVersion(Start.TargetPkg()).end() == false) + { + /* Before removing or keeping the package with the broken dependency + try instead to install the first not previously installed package + solving this dependency. This helps every time a previous solver + is removed by the resolver because of a conflict or alike but it is + dangerous as it could trigger new breaks/conflicts… */ + if (Debug == true) + clog << " Try Installing " << APT::PrettyPkg(&Cache, Start.TargetPkg()) << " before changing " << I.FullName(false) << std::endl; + auto const OldBroken = Cache.BrokenCount(); + Cache.MarkInstall(Start.TargetPkg(), true, 1, false); + // FIXME: we should undo the complete MarkInstall process here + if (Cache[Start.TargetPkg()].InstBroken() == true || Cache.BrokenCount() > OldBroken) + Cache.MarkDelete(Start.TargetPkg(), false, 1, false); + } + } + } + + Change = true; + Done = true; + break; + } + else + { + if (Start->Type == pkgCache::Dep::DpkgBreaks) + { + // first, try upgradring the package, if that + // does not help, the breaks goes onto the + // kill list + // + // FIXME: use DoUpgrade(Pkg) instead? + if (Cache[End] & pkgDepCache::DepGCVer) + { + if (Debug) + clog << " Upgrading " << Pkg.FullName(false) << " due to Breaks field in " << I.FullName(false) << endl; + Cache.MarkInstall(Pkg, false, 0, false); + continue; + } + } + + // Skip adding to the kill list if it is protected + if ((Flags[Pkg->ID] & Protected) != 0) + continue; + + if (Debug == true) + clog << " Added " << Pkg.FullName(false) << " to the remove list" << endl; + + KillList.push_back({Pkg, End}); + + if (Start.IsNegative() == false) + break; + } + } + + // Hm, nothing can possibly satisfy this dep. Nuke it. + if (VList[0] == 0 && + Start.IsNegative() == false && + (Flags[I->ID] & Protected) != Protected) + { + bool Installed = Cache[I].Install(); + Cache.MarkKeep(I); + if (Cache[I].InstBroken() == false) + { + // Unwind operation will be keep now + if (OrOp == OrRemove) + OrOp = OrKeep; + + // Restore + if (InOr == true && Installed == true) + Cache.MarkInstall(I, false, 0, false); + + if (Debug == true) + clog << " Holding Back " << I.FullName(false) << " because I can't find " << Start.TargetPkg().FullName(false) << endl; + } + else + { + if (Debug == true) + clog << " Removing " << I.FullName(false) << " because I can't find " << Start.TargetPkg().FullName(false) << endl; + if (InOr == false) + Cache.MarkDelete(I, false, 0, false); + } + + Change = true; + Done = true; + } + + // Try some more + if (InOr == true) + continue; + + if (Done == true) + break; + } + + // Apply the kill list now + if (Cache[I].InstallVer != 0) + { + for (auto J = KillList.begin(); J != KillList.end(); J++) + { + Change = true; + if ((Cache[J->Dep] & pkgDepCache::DepGNow) == 0) + { + if (J->Dep.IsNegative() == true) + { + if (Debug == true) + clog << " Fixing " << I.FullName(false) << " via remove of " << J->Pkg.FullName(false) << endl; + Cache.MarkDelete(J->Pkg, false, 0, false); + } + } + else + { + if (Debug == true) + clog << " Fixing " << I.FullName(false) << " via keep of " << J->Pkg.FullName(false) << endl; + Cache.MarkKeep(J->Pkg, false, false); + } + + if (Counter > 1) + { + if (Scores[I->ID] > Scores[J->Pkg->ID]) + Scores[J->Pkg->ID] = Scores[I->ID]; + } + } + } + } + } + + if (Debug == true) + clog << "Done" << endl; + + if (Cache.BrokenCount() != 0) + { + // See if this is the result of a hold + pkgCache::PkgIterator I = Cache.PkgBegin(); + for (;I.end() != true; ++I) + { + if (Cache[I].InstBroken() == false) + continue; + if ((Flags[I->ID] & Protected) != Protected) + return _error->Error(_("Error, pkgProblemResolver::Resolve generated breaks, this may be caused by held packages.")); + } + return _error->Error(_("Unable to correct problems, you have held broken packages.")); + } + + // set the auto-flags (mvo: I'm not sure if we _really_ need this) + pkgCache::PkgIterator I = Cache.PkgBegin(); + for (;I.end() != true; ++I) { + if (Cache[I].NewInstall() && !(Flags[I->ID] & PreInstalled)) { + if(_config->FindB("Debug::pkgAutoRemove",false)) { + std::clog << "Resolve installed new pkg: " << I.FullName(false) + << " (now marking it as auto)" << std::endl; + } + Cache[I].Flags |= pkgCache::Flag::Auto; + } + } + + + return true; +} + /*}}}*/ +// ProblemResolver::BreaksInstOrPolicy - Check if the given pkg is broken/*{{{*/ +// --------------------------------------------------------------------- +/* This checks if the given package is broken either by a hard dependency + (InstBroken()) or by introducing a new policy breakage e.g. new + unsatisfied recommends for a package that was in "policy-good" state + + Note that this is not perfect as it will ignore further breakage + for already broken policy (recommends) +*/ +bool pkgProblemResolver::InstOrNewPolicyBroken(pkgCache::PkgIterator I) +{ + // a broken install is always a problem + if (Cache[I].InstBroken() == true) + { + if (Debug == true) + std::clog << " Dependencies are not satisfied for " << APT::PrettyPkg(&Cache, I) << std::endl; + return true; + } + + // a newly broken policy (recommends/suggests) is a problem + if (Cache[I].NowPolicyBroken() == false && + Cache[I].InstPolicyBroken() == true) + { + if (Debug == true) + std::clog << " Policy breaks with upgrade of " << APT::PrettyPkg(&Cache, I) << std::endl; + return true; + } + + return false; +} + /*}}}*/ +// ProblemResolver::ResolveByKeep - Resolve problems using keep /*{{{*/ +// --------------------------------------------------------------------- +/* This is the work horse of the soft upgrade routine. It is very gentle + in that it does not install or remove any packages. It is assumed that the + system was non-broken previously. */ +bool pkgProblemResolver::ResolveByKeep(OpProgress * const Progress) +{ + std::string const solver = _config->Find("APT::Solver", "internal"); + constexpr auto flags = EDSP::Request::UPGRADE_ALL | EDSP::Request::FORBID_NEW_INSTALL | EDSP::Request::FORBID_REMOVE; + auto const ret = EDSP::ResolveExternal(solver.c_str(), Cache, flags, Progress); + if (solver != "internal") + return ret; + return ResolveByKeepInternal(); +} + /*}}}*/ +// ProblemResolver::ResolveByKeepInternal - Resolve problems using keep /*{{{*/ +// --------------------------------------------------------------------- +/* This is the work horse of the soft upgrade routine. It is very gentle + in that it does not install or remove any packages. It is assumed that the + system was non-broken previously. */ +bool pkgProblemResolver::ResolveByKeepInternal() +{ + pkgDepCache::ActionGroup group(Cache); + + MakeScores(); + + /* We have to order the packages so that the broken fixing pass + operates from highest score to lowest. This prevents problems when + high score packages cause the removal of lower score packages that + would cause the removal of even lower score packages. */ + auto Size = Cache.Head().PackageCount; + pkgCache::Package **PList = new pkgCache::Package *[Size]; + pkgCache::Package **PEnd = PList; + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + *PEnd++ = I; + + std::sort(PList,PEnd,[this](Package *a, Package *b) { return ScoreSort(a, b) < 0; }); + + + if (_config->FindB("Debug::pkgProblemResolver::ShowScores",false) == true) + { + clog << "Show Scores" << endl; + for (pkgCache::Package **K = PList; K != PEnd; K++) + if (Scores[(*K)->ID] != 0) + { + pkgCache::PkgIterator Pkg(Cache,*K); + clog << Scores[(*K)->ID] << ' ' << APT::PrettyPkg(&Cache, Pkg) << std::endl; + } + } + + if (Debug == true) + clog << "Entering ResolveByKeep" << endl; + + // Consider each broken package + pkgCache::Package **LastStop = 0; + for (pkgCache::Package **K = PList; K != PEnd; K++) + { + pkgCache::PkgIterator I(Cache,*K); + + if (Cache[I].InstallVer == 0) + continue; + + if (InstOrNewPolicyBroken(I) == false) + continue; + + /* Keep the package. If this works then great, otherwise we have + to be significantly more aggressive and manipulate its dependencies */ + if ((Flags[I->ID] & Protected) == 0) + { + if (Debug == true) + clog << "Keeping package " << I.FullName(false) << endl; + Cache.MarkKeep(I, false, false); + if (InstOrNewPolicyBroken(I) == false) + { + K = PList - 1; + continue; + } + } + + // Isolate the problem dependencies + for (pkgCache::DepIterator D = Cache[I].InstVerIter(Cache).DependsList(); D.end() == false;) + { + DepIterator Start; + DepIterator End; + D.GlobOr(Start,End); + + // We only worry about critical deps. + if (End.IsCritical() != true) + continue; + + // Dep is ok + if ((Cache[End] & pkgDepCache::DepGInstall) == pkgDepCache::DepGInstall) + continue; + + /* Hm, the group is broken.. I suppose the best thing to do is to + is to try every combination of keep/not-keep for the set, but that's + slow, and this never happens, just be conservative and assume the + list of ors is in preference and keep till it starts to work. */ + while (true) + { + if (Debug == true) + clog << "Package " << I.FullName(false) << " " << APT::PrettyDep(&Cache, Start) << endl; + + // Look at all the possible provides on this package + std::unique_ptr<pkgCache::Version *[]> VList(Start.AllTargets()); + for (pkgCache::Version **V = VList.get(); *V != 0; V++) + { + pkgCache::VerIterator Ver(Cache,*V); + pkgCache::PkgIterator Pkg = Ver.ParentPkg(); + + // It is not keepable + if (Cache[Pkg].InstallVer == 0 || + Pkg->CurrentVer == 0) + continue; + + if ((Flags[I->ID] & Protected) == 0) + { + if (Debug == true) + clog << " Keeping Package " << Pkg.FullName(false) << " due to " << Start.DepType() << endl; + Cache.MarkKeep(Pkg, false, false); + } + + if (InstOrNewPolicyBroken(I) == false) + break; + } + + if (InstOrNewPolicyBroken(I) == false) + break; + + if (Start == End) + break; + ++Start; + } + + if (InstOrNewPolicyBroken(I) == false) + break; + } + + if (InstOrNewPolicyBroken(I) == true) + continue; + + // Restart again. + if (K == LastStop) { + // I is an iterator based off our temporary package list, + // so copy the name we need before deleting the temporary list + std::string const LoopingPackage = I.FullName(false); + delete[] PList; + return _error->Error("Internal Error, pkgProblemResolver::ResolveByKeep is looping on package %s.", LoopingPackage.c_str()); + } + LastStop = K; + K = PList - 1; + } + + delete[] PList; + return true; +} + /*}}}*/ +// ProblemResolver::InstallProtect - deprecated cpu-eating no-op /*{{{*/ +// --------------------------------------------------------------------- +/* Actions issued with FromUser bit set are protected from further + modification (expect by other calls with FromUser set) nowadays , so we + don't need to reissue actions here, they are already set in stone. */ +void pkgProblemResolver::InstallProtect() +{ + pkgDepCache::ActionGroup group(Cache); + + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + if ((Flags[I->ID] & Protected) == Protected) + { + if ((Flags[I->ID] & ToRemove) == ToRemove) + Cache.MarkDelete(I); + else + { + // preserve the information whether the package was auto + // or manually installed + bool autoInst = (Cache[I].Flags & pkgCache::Flag::Auto); + Cache.MarkInstall(I, false, 0, !autoInst); + } + } + } +} + /*}}}*/ +// PrioSortList - Sort a list of versions by priority /*{{{*/ +// --------------------------------------------------------------------- +/* This is meant to be used in conjunction with AllTargets to get a list + of versions ordered by preference. */ + +struct PrioComp { + pkgCache &PrioCache; + + explicit PrioComp(pkgCache &PrioCache) : PrioCache(PrioCache) { + } + + bool operator() (pkgCache::Version * const &A, pkgCache::Version * const &B) { + return compare(A, B) < 0; + } + + int compare(pkgCache::Version * const &A, pkgCache::Version * const &B) { + pkgCache::VerIterator L(PrioCache,A); + pkgCache::VerIterator R(PrioCache,B); + + if ((L.ParentPkg()->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential && + (R.ParentPkg()->Flags & pkgCache::Flag::Essential) != pkgCache::Flag::Essential) + return 1; + if ((L.ParentPkg()->Flags & pkgCache::Flag::Essential) != pkgCache::Flag::Essential && + (R.ParentPkg()->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential) + return -1; + + if ((L.ParentPkg()->Flags & pkgCache::Flag::Important) == pkgCache::Flag::Important && + (R.ParentPkg()->Flags & pkgCache::Flag::Important) != pkgCache::Flag::Important) + return 1; + if ((L.ParentPkg()->Flags & pkgCache::Flag::Important) != pkgCache::Flag::Important && + (R.ParentPkg()->Flags & pkgCache::Flag::Important) == pkgCache::Flag::Important) + return -1; + + if (L->Priority != R->Priority) + return R->Priority - L->Priority; + return strcmp(L.ParentPkg().Name(),R.ParentPkg().Name()); + } +}; + +void pkgPrioSortList(pkgCache &Cache,pkgCache::Version **List) +{ + unsigned long Count = 0; + for (pkgCache::Version **I = List; *I != 0; I++) + Count++; + std::sort(List,List+Count,PrioComp(Cache)); +} + /*}}}*/ diff --git a/apt-pkg/algorithms.h b/apt-pkg/algorithms.h new file mode 100644 index 0000000..c5c9f4c --- /dev/null +++ b/apt-pkg/algorithms.h @@ -0,0 +1,162 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Algorithms - A set of misc algorithms + + This simulate class displays what the ordering code has done and + analyses it with a fresh new dependency cache. In this way we can + see all of the effects of an upgrade run. + + pkgDistUpgrade computes an upgrade that causes as many packages as + possible to move to the newest version. + + pkgApplyStatus sets the target state based on the content of the status + field in the status file. It is important to get proper crash recovery. + + pkgFixBroken corrects a broken system so that it is in a sane state. + + pkgAllUpgrade attempts to upgade as many packages as possible but + without installing new packages. + + The problem resolver class contains a number of complex algorithms + to try to best-guess an upgrade state. It solves the problem of + maximizing the number of install state packages while having no broken + packages. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_ALGORITHMS_H +#define PKGLIB_ALGORITHMS_H + +#include <apt-pkg/depcache.h> +#include <apt-pkg/packagemanager.h> +#include <apt-pkg/pkgcache.h> + +#include <iostream> +#include <string> + +#include <apt-pkg/macros.h> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/acquire.h> +using std::ostream; +#endif + +#ifndef APT_9_CLEANER_HEADERS +// include pkg{DistUpgrade,AllUpgrade,MiniizeUpgrade} here for compatibility +#include <apt-pkg/update.h> +#include <apt-pkg/upgrade.h> +#endif + + +class pkgSimulatePrivate; +class pkgSimulate : public pkgPackageManager /*{{{*/ +{ + pkgSimulatePrivate * const d; + protected: + + class Policy : public pkgDepCache::Policy + { + pkgDepCache *Cache; + public: + + virtual VerIterator GetCandidateVer(PkgIterator const &Pkg) APT_OVERRIDE + { + return (*Cache)[Pkg].CandidateVerIter(*Cache); + } + + explicit Policy(pkgDepCache *Cache) : Cache(Cache) {}; + }; + + unsigned char *Flags; + + Policy iPolicy; + pkgDepCache Sim; + pkgDepCache::ActionGroup group; + + // The Actual installation implementation + virtual bool Install(PkgIterator Pkg,std::string File) APT_OVERRIDE; + virtual bool Configure(PkgIterator Pkg) APT_OVERRIDE; + virtual bool Remove(PkgIterator Pkg,bool Purge) APT_OVERRIDE; + + // FIXME: trick to avoid ABI break for virtual reimplementation; fix on next ABI break +public: + APT_HIDDEN bool Go2(APT::Progress::PackageManager * progress); + +private: + APT_HIDDEN void ShortBreaks(); + APT_HIDDEN void Describe(PkgIterator iPkg,std::ostream &out,bool Current,bool Candidate); + APT_HIDDEN bool RealInstall(PkgIterator Pkg,std::string File); + APT_HIDDEN bool RealConfigure(PkgIterator Pkg); + APT_HIDDEN bool RealRemove(PkgIterator Pkg,bool Purge); + + public: + + explicit pkgSimulate(pkgDepCache *Cache); + virtual ~pkgSimulate(); +}; + /*}}}*/ +class pkgProblemResolver /*{{{*/ +{ + private: + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + pkgDepCache &Cache; + typedef pkgCache::PkgIterator PkgIterator; + typedef pkgCache::VerIterator VerIterator; + typedef pkgCache::DepIterator DepIterator; + typedef pkgCache::PrvIterator PrvIterator; + typedef pkgCache::Version Version; + typedef pkgCache::Package Package; + + enum Flags {Protected = (1 << 0), PreInstalled = (1 << 1), + Upgradable = (1 << 2), ReInstateTried = (1 << 3), + ToRemove = (1 << 4)}; + int *Scores; + unsigned char *Flags; + bool Debug; + + // Sort stuff + APT_HIDDEN int ScoreSort(Package const *A, Package const *B) APT_PURE; + + struct PackageKill + { + PkgIterator Pkg; + DepIterator Dep; + }; + + APT_HIDDEN void MakeScores(); + APT_HIDDEN bool DoUpgrade(pkgCache::PkgIterator Pkg); + + protected: + bool InstOrNewPolicyBroken(pkgCache::PkgIterator Pkg); + + public: + + inline void Protect(pkgCache::PkgIterator Pkg) {Flags[Pkg->ID] |= Protected; Cache.MarkProtected(Pkg);}; + inline void Remove(pkgCache::PkgIterator Pkg) {Flags[Pkg->ID] |= ToRemove;}; + inline void Clear(pkgCache::PkgIterator Pkg) {Flags[Pkg->ID] &= ~(Protected | ToRemove);}; + + // Try to intelligently resolve problems by installing and removing packages + bool Resolve(bool BrokenFix = false, OpProgress * const Progress = NULL); + APT_HIDDEN bool ResolveInternal(bool const BrokenFix = false); + + // Try to resolve problems only by using keep + bool ResolveByKeep(OpProgress * const Progress = NULL); + APT_HIDDEN bool ResolveByKeepInternal(); + + APT_DEPRECATED_MSG("NOOP as MarkInstall enforces not overriding FromUser markings") void InstallProtect(); + + explicit pkgProblemResolver(pkgDepCache *Cache); + virtual ~pkgProblemResolver(); +}; + /*}}}*/ +bool pkgApplyStatus(pkgDepCache &Cache); +bool pkgFixBroken(pkgDepCache &Cache); + +void pkgPrioSortList(pkgCache &Cache,pkgCache::Version **List); + + +#endif diff --git a/apt-pkg/aptconfiguration.cc b/apt-pkg/aptconfiguration.cc new file mode 100644 index 0000000..61e53ec --- /dev/null +++ b/apt-pkg/aptconfiguration.cc @@ -0,0 +1,483 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Provide access methods to various configuration settings, + setup defaults and returns validate settings. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgsystem.h> +#include <apt-pkg/strutl.h> + +#include <algorithm> +#include <string> +#include <vector> +#include <ctype.h> +#include <dirent.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + /*}}}*/ +namespace APT { +// setDefaultConfigurationForCompressors /*{{{*/ +static void setDefaultConfigurationForCompressors() { + // Set default application paths to check for optional compression types + _config->CndSet("Dir::Bin::gzip", "/bin/gzip"); + _config->CndSet("Dir::Bin::bzip2", "/bin/bzip2"); + _config->CndSet("Dir::Bin::xz", "/usr/bin/xz"); + _config->CndSet("Dir::Bin::lz4", "/usr/bin/lz4"); + _config->CndSet("Dir::Bin::zstd", "/usr/bin/zstd"); + if (FileExists(_config->Find("Dir::Bin::xz")) == true) { + _config->Set("Dir::Bin::lzma", _config->Find("Dir::Bin::xz")); + _config->Set("APT::Compressor::lzma::Binary", "xz"); + if (_config->Exists("APT::Compressor::lzma::CompressArg") == false) { + _config->Set("APT::Compressor::lzma::CompressArg::", "--format=lzma"); + _config->Set("APT::Compressor::lzma::CompressArg::", "-6"); + } + if (_config->Exists("APT::Compressor::lzma::UncompressArg") == false) { + _config->Set("APT::Compressor::lzma::UncompressArg::", "--format=lzma"); + _config->Set("APT::Compressor::lzma::UncompressArg::", "-d"); + } + } else { + _config->CndSet("Dir::Bin::lzma", "/usr/bin/lzma"); + if (_config->Exists("APT::Compressor::lzma::CompressArg") == false) { + _config->Set("APT::Compressor::lzma::CompressArg::", "--suffix="); + _config->Set("APT::Compressor::lzma::CompressArg::", "-6"); + } + if (_config->Exists("APT::Compressor::lzma::UncompressArg") == false) { + _config->Set("APT::Compressor::lzma::UncompressArg::", "--suffix="); + _config->Set("APT::Compressor::lzma::UncompressArg::", "-d"); + } + } + // setup the defaults for the compressiontypes => method mapping + _config->CndSet("Acquire::CompressionTypes::xz","xz"); + _config->CndSet("Acquire::CompressionTypes::bz2","bzip2"); + _config->CndSet("Acquire::CompressionTypes::lzma","lzma"); + _config->CndSet("Acquire::CompressionTypes::gz","gzip"); + _config->CndSet("Acquire::CompressionTypes::lz4","lz4"); + _config->CndSet("Acquire::CompressionTypes::zst", "zstd"); +} + /*}}}*/ +// getCompressionTypes - Return Vector of usable compressiontypes /*{{{*/ +// --------------------------------------------------------------------- +/* return a vector of compression types in the preferred order. */ +std::vector<std::string> +const Configuration::getCompressionTypes(bool const &Cached) { + static std::vector<std::string> types; + if (types.empty() == false) { + if (Cached == true) + return types; + else + types.clear(); + } + + std::vector<APT::Configuration::Compressor> const compressors = getCompressors(); + + // load the order setting into our vector + std::vector<std::string> const order = _config->FindVector("Acquire::CompressionTypes::Order"); + for (std::vector<std::string>::const_iterator o = order.begin(); + o != order.end(); ++o) { + if ((*o).empty() == true) + continue; + // ignore types we have no method ready to use + std::string const method = std::string("Acquire::CompressionTypes::").append(*o); + if (_config->Exists(method) == false) + continue; + // ignore types we have no app ready to use + std::string const app = _config->Find(method); + if (std::find_if(compressors.begin(), compressors.end(), [&app](APT::Configuration::Compressor const &c) { + return c.Name == app; + }) == compressors.end()) + continue; + types.push_back(*o); + } + + // move again over the option tree to add all missing compression types + ::Configuration::Item const *Types = _config->Tree("Acquire::CompressionTypes"); + if (Types != 0) + Types = Types->Child; + + for (; Types != 0; Types = Types->Next) { + if (Types->Tag == "Order" || Types->Tag.empty() == true) + continue; + // ignore types we already have in the vector + if (std::find(types.begin(),types.end(),Types->Tag) != types.end()) + continue; + // ignore types we have no app ready to use + if (std::find_if(compressors.begin(), compressors.end(), [&Types](APT::Configuration::Compressor const &c) { + return c.Name == Types->Value; + }) == compressors.end()) + continue; + types.push_back(Types->Tag); + } + + // add the special "uncompressed" type + if (std::find(types.begin(), types.end(), "uncompressed") == types.end()) + { + std::string const uncompr = _config->Find("Dir::Bin::uncompressed", ""); + if (uncompr.empty() == true || FileExists(uncompr) == true) + types.push_back("uncompressed"); + } + + return types; +} + /*}}}*/ +// GetLanguages - Return Vector of Language Codes /*{{{*/ +// --------------------------------------------------------------------- +/* return a vector of language codes in the preferred order. + the special word "environment" will be replaced with the long and the short + code of the local settings and it will be insured that this will not add + duplicates. So in an german local the setting "environment, de_DE, en, de" + will result in "de_DE, de, en". + The special word "none" is the stopcode for the not-All code vector */ +std::vector<std::string> const Configuration::getLanguages(bool const &All, + bool const &Cached, char const ** const Locale) { + using std::string; + + // The detection is boring and has a lot of cornercases, + // so we cache the results to calculated it only once. + std::vector<string> static allCodes; + std::vector<string> static codes; + + // we have something in the cache + if (codes.empty() == false || allCodes.empty() == false) { + if (Cached == true) { + if(All == true && allCodes.empty() == false) + return allCodes; + else + return codes; + } else { + allCodes.clear(); + codes.clear(); + } + } + + // Include all Language codes we have a Translation file for in /var/lib/apt/lists + // so they will be all included in the Cache. + std::vector<string> builtin; + DIR *D = opendir(_config->FindDir("Dir::State::lists").c_str()); + if (D != NULL) { + builtin.push_back("none"); + for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D)) { + string const name = SubstVar(Ent->d_name, "%5f", "_"); + size_t const foundDash = name.rfind("-"); + size_t const foundUnderscore = name.rfind("_", foundDash); + if (foundDash == string::npos || foundUnderscore == string::npos || + foundDash <= foundUnderscore || + name.substr(foundUnderscore+1, foundDash-(foundUnderscore+1)) != "Translation") + continue; + string const c = name.substr(foundDash+1); + if (unlikely(c.empty() == true) || c == "en") + continue; + // Skip unusual files, like backups or that alike + string::const_iterator s = c.begin(); + for (;s != c.end(); ++s) { + if (isalpha(*s) == 0 && *s != '_') + break; + } + if (s != c.end()) + continue; + if (std::find(builtin.begin(), builtin.end(), c) != builtin.end()) + continue; + builtin.push_back(c); + } + closedir(D); + } + + // FIXME: Remove support for the old APT::Acquire::Translation + // it was undocumented and so it should be not very widthly used + string const oldAcquire = _config->Find("APT::Acquire::Translation",""); + if (oldAcquire.empty() == false && oldAcquire != "environment") { + // TRANSLATORS: the two %s are APT configuration options + _error->Notice("Option '%s' is deprecated. Please use '%s' instead, see 'man 5 apt.conf' for details.", + "APT::Acquire::Translation", "Acquire::Languages"); + if (oldAcquire != "none") + codes.push_back(oldAcquire); + codes.push_back("en"); + allCodes = codes; + for (std::vector<string>::const_iterator b = builtin.begin(); + b != builtin.end(); ++b) + if (std::find(allCodes.begin(), allCodes.end(), *b) == allCodes.end()) + allCodes.push_back(*b); + if (All == true) + return allCodes; + else + return codes; + } + + // get the environment language codes: LC_MESSAGES (and later LANGUAGE) + // we extract both, a long and a short code and then we will + // check if we actually need both (rare) or if the short is enough + string const envMsg = string(Locale == 0 ? ::setlocale(LC_MESSAGES, NULL) : *Locale); + size_t const lenShort = (envMsg.find('_') != string::npos) ? envMsg.find('_') : 2; + size_t const lenLong = (envMsg.find_first_of(".@") != string::npos) ? envMsg.find_first_of(".@") : (lenShort + 3); + + string const envLong = envMsg.substr(0,lenLong); + string const envShort = envLong.substr(0,lenShort); + + // It is very likely we will need the environment codes later, + // so let us generate them now from LC_MESSAGES and LANGUAGE + std::vector<string> environment; + if (envShort != "C") { + // take care of LC_MESSAGES + if (envLong != envShort) + environment.push_back(envLong); + environment.push_back(envShort); + // take care of LANGUAGE + const char *language_env = getenv("LANGUAGE") == 0 ? "" : getenv("LANGUAGE"); + string envLang = Locale == 0 ? language_env : *(Locale+1); + if (envLang.empty() == false) { + std::vector<string> env = VectorizeString(envLang,':'); + short addedLangs = 0; // add a maximum of 3 fallbacks from the environment + for (std::vector<string>::const_iterator e = env.begin(); + e != env.end() && addedLangs < 3; ++e) { + if (unlikely(e->empty() == true) || *e == "en") + continue; + if (*e == envLong || *e == envShort) + continue; + if (std::find(environment.begin(), environment.end(), *e) != environment.end()) + continue; + ++addedLangs; + environment.push_back(*e); + } + } + } else { + // cornercase: LANG=C, so we use only "en" Translation + environment.push_back("en"); + } + + std::vector<string> const lang = _config->FindVector("Acquire::Languages", "environment,en"); + // the configs define the order, so add the environment + // then needed and ensure the codes are not listed twice. + bool noneSeen = false; + for (std::vector<string>::const_iterator l = lang.begin(); + l != lang.end(); ++l) { + if (*l == "environment") { + for (std::vector<string>::const_iterator e = environment.begin(); + e != environment.end(); ++e) { + if (std::find(allCodes.begin(), allCodes.end(), *e) != allCodes.end()) + continue; + if (noneSeen == false) + codes.push_back(*e); + allCodes.push_back(*e); + } + continue; + } else if (*l == "none") { + noneSeen = true; + continue; + } else if (std::find(allCodes.begin(), allCodes.end(), *l) != allCodes.end()) + continue; + + if (noneSeen == false) + codes.push_back(*l); + allCodes.push_back(*l); + } + + if (allCodes.empty() == false) { + for (std::vector<string>::const_iterator b = builtin.begin(); + b != builtin.end(); ++b) + if (std::find(allCodes.begin(), allCodes.end(), *b) == allCodes.end()) + allCodes.push_back(*b); + } else { + // "none" was forced + allCodes.push_back("none"); + } + + if (All == true) + return allCodes; + else + return codes; +} + /*}}}*/ +// checkLanguage - are we interested in the given Language? /*{{{*/ +bool Configuration::checkLanguage(std::string Lang, bool const All) { + // the empty Language is always interesting as it is the original + if (Lang.empty() == true) + return true; + // filenames are encoded, so undo this + Lang = SubstVar(Lang, "%5f", "_"); + std::vector<std::string> const langs = getLanguages(All, true); + return (std::find(langs.begin(), langs.end(), Lang) != langs.end()); +} + /*}}}*/ +// getArchitectures - Return Vector of preferred Architectures /*{{{*/ +std::vector<std::string> const Configuration::getArchitectures(bool const &Cached) { + using std::string; + + std::vector<string> static archs; + if (likely(Cached == true) && archs.empty() == false) + return archs; + + string const arch = _config->Find("APT::Architecture"); + archs = _config->FindVector("APT::Architectures"); + + if (archs.empty() == true && _system != nullptr) + archs = _system->ArchitecturesSupported(); + + if (archs.empty() == true || + std::find(archs.begin(), archs.end(), arch) == archs.end()) + archs.insert(archs.begin(), arch); + + // erase duplicates and empty strings + for (std::vector<string>::reverse_iterator a = archs.rbegin(); + a != archs.rend(); ++a) { + if (a->empty() == true || std::find(a + 1, archs.rend(), *a) != archs.rend()) + archs.erase(a.base()-1); + if (a == archs.rend()) + break; + } + + return archs; +} + /*}}}*/ +// checkArchitecture - are we interested in the given Architecture? /*{{{*/ +bool Configuration::checkArchitecture(std::string const &Arch) { + if (Arch == "all") + return true; + std::vector<std::string> const archs = getArchitectures(true); + return (std::find(archs.begin(), archs.end(), Arch) != archs.end()); +} + /*}}}*/ +// getCompressors - Return Vector of usealbe compressors /*{{{*/ +// --------------------------------------------------------------------- +/* return a vector of compressors used by apt-ftparchive in the + multicompress functionality or to detect data.tar files */ +std::vector<APT::Configuration::Compressor> +const Configuration::getCompressors(bool const Cached) { + static std::vector<APT::Configuration::Compressor> compressors; + if (compressors.empty() == false) { + if (Cached == true) + return compressors; + else + compressors.clear(); + } + + setDefaultConfigurationForCompressors(); + + std::vector<std::string> CompressorsDone; +# define APT_ADD_COMPRESSOR(NAME, EXT, BINARY, ARG, DEARG, COST) \ + { CompressorsDone.push_back(NAME); compressors.emplace_back(NAME, EXT, BINARY, ARG, DEARG, COST); } + APT_ADD_COMPRESSOR(".", "", "", nullptr, nullptr, 0) + if (_config->Exists("Dir::Bin::zstd") == false || FileExists(_config->Find("Dir::Bin::zstd")) == true) + APT_ADD_COMPRESSOR("zstd", ".zst", "zstd", "-19", "-d", 60) +#ifdef HAVE_ZSTD + else + APT_ADD_COMPRESSOR("zstd", ".zst", "false", nullptr, nullptr, 60) +#endif + if (_config->Exists("Dir::Bin::lz4") == false || FileExists(_config->Find("Dir::Bin::lz4")) == true) + APT_ADD_COMPRESSOR("lz4",".lz4","lz4","-1","-d",50) +#ifdef HAVE_LZ4 + else + APT_ADD_COMPRESSOR("lz4",".lz4","false", nullptr, nullptr, 50) +#endif + if (_config->Exists("Dir::Bin::gzip") == false || FileExists(_config->Find("Dir::Bin::gzip")) == true) + APT_ADD_COMPRESSOR("gzip",".gz","gzip","-6n","-d",100) +#ifdef HAVE_ZLIB + else + APT_ADD_COMPRESSOR("gzip",".gz","false", nullptr, nullptr, 100) +#endif + if (_config->Exists("Dir::Bin::xz") == false || FileExists(_config->Find("Dir::Bin::xz")) == true) + APT_ADD_COMPRESSOR("xz",".xz","xz","-6","-d",200) +#ifdef HAVE_LZMA + else + APT_ADD_COMPRESSOR("xz",".xz","false", nullptr, nullptr, 200) +#endif + if (_config->Exists("Dir::Bin::bzip2") == false || FileExists(_config->Find("Dir::Bin::bzip2")) == true) + APT_ADD_COMPRESSOR("bzip2",".bz2","bzip2","-6","-d",300) +#ifdef HAVE_BZ2 + else + APT_ADD_COMPRESSOR("bzip2",".bz2","false", nullptr, nullptr, 300) +#endif + if (_config->Exists("Dir::Bin::lzma") == false || FileExists(_config->Find("Dir::Bin::lzma")) == true) + APT_ADD_COMPRESSOR("lzma",".lzma","lzma","-6","-d",400) +#ifdef HAVE_LZMA + else + APT_ADD_COMPRESSOR("lzma",".lzma","false", nullptr, nullptr, 400) +#endif + + std::vector<std::string> const comp = _config->FindVector("APT::Compressor", "", true); + for (auto const &c: comp) + { + if (c.empty() || std::find(CompressorsDone.begin(), CompressorsDone.end(), c) != CompressorsDone.end()) + continue; + compressors.push_back(Compressor(c.c_str(), std::string(".").append(c).c_str(), c.c_str(), nullptr, nullptr, 1000)); + } + + return compressors; +} + /*}}}*/ +// getCompressorExtensions - supported data.tar extensions /*{{{*/ +// --------------------------------------------------------------------- +/* */ +std::vector<std::string> const Configuration::getCompressorExtensions() { + std::vector<APT::Configuration::Compressor> const compressors = getCompressors(); + std::vector<std::string> ext; + for (std::vector<APT::Configuration::Compressor>::const_iterator c = compressors.begin(); + c != compressors.end(); ++c) + if (c->Extension.empty() == false && c->Extension != ".") + ext.push_back(c->Extension); + return ext; +} + /*}}}*/ +// Compressor constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +Configuration::Compressor::Compressor(char const *name, char const *extension, + char const *binary, + char const *compressArg, char const *uncompressArg, + unsigned short const cost) { + std::string const config = std::string("APT::Compressor::").append(name).append("::"); + Name = _config->Find(std::string(config).append("Name"), name); + Extension = _config->Find(std::string(config).append("Extension"), extension); + Binary = _config->Find(std::string(config).append("Binary"), binary); + Cost = _config->FindI(std::string(config).append("Cost"), cost); + std::string const compConf = std::string(config).append("CompressArg"); + if (_config->Exists(compConf) == true) + CompressArgs = _config->FindVector(compConf); + else if (compressArg != NULL) + CompressArgs.push_back(compressArg); + std::string const uncompConf = std::string(config).append("UncompressArg"); + if (_config->Exists(uncompConf) == true) + UncompressArgs = _config->FindVector(uncompConf); + else if (uncompressArg != NULL) + UncompressArgs.push_back(uncompressArg); +} + /*}}}*/ +// getBuildProfiles - return a vector of enabled build profiles /*{{{*/ +std::vector<std::string> const Configuration::getBuildProfiles() { + // order is: override value (~= commandline), environment variable, list (~= config file) + std::string profiles_env = getenv("DEB_BUILD_PROFILES") == 0 ? "" : getenv("DEB_BUILD_PROFILES"); + if (profiles_env.empty() == false) { + profiles_env = SubstVar(profiles_env, " ", ","); + std::string const bp = _config->Find("APT::Build-Profiles"); + _config->Clear("APT::Build-Profiles"); + if (bp.empty() == false) + _config->Set("APT::Build-Profiles", bp); + } + return _config->FindVector("APT::Build-Profiles", profiles_env); +} +std::string const Configuration::getBuildProfilesString() { + std::vector<std::string> profiles = getBuildProfiles(); + if (profiles.empty() == true) + return ""; + std::vector<std::string>::const_iterator p = profiles.begin(); + std::string list = *p; + for (++p; p != profiles.end(); ++p) + list.append(",").append(*p); + return list; +} + /*}}}*/ +} diff --git a/apt-pkg/aptconfiguration.h b/apt-pkg/aptconfiguration.h new file mode 100644 index 0000000..34621a2 --- /dev/null +++ b/apt-pkg/aptconfiguration.h @@ -0,0 +1,128 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/** \class APT::Configuration + * \brief Provide access methods to various configuration settings + * + * This class and their methods providing a layer around the usual access + * methods with _config to ensure that settings are correct and to be able + * to set defaults without the need to recheck it in every method again. + */ + /*}}}*/ +#ifndef APT_CONFIGURATION_H +#define APT_CONFIGURATION_H +// Include Files /*{{{*/ +#include <limits> +#include <string> +#include <vector> + /*}}}*/ +namespace APT { +namespace Configuration { /*{{{*/ + /** \brief Returns a vector of usable Compression Types + * + * Files can be compressed in various ways to decrease the size of the + * download. Therefore the Acquiremethods support a few compression types + * and some archives provide also a few different types. This option + * group exists to give the user the choice to prefer one type over the + * other (some compression types are very resource intensive - great if you + * have a limited download, bad if you have a really lowpowered hardware.) + * + * This method ensures that the defaults are set and checks at runtime + * if the type can be used. E.g. the current default is to prefer bzip2 + * over lzma and gz - if the bzip2 binary is not available it has not much + * sense in downloading the bz2 file, therefore we will not return bz2 as + * a usable compression type. The availability is checked with the settings + * in the Dir::Bin group. + * + * \param Cached saves the result so we need to calculated it only once + * this parameter should only be used for testing purposes. + * + * \return a vector of the compression types in the preferred usage order + */ + std::vector<std::string> const getCompressionTypes(bool const &Cached = true); + + /** \brief Returns a vector of Language Codes + * + * Languages can be defined with their two or five chars long code. + * This methods handles the various ways to set the preferred codes, + * honors the environment and ensures that the codes are not listed twice. + * + * The special word "environment" will be replaced with the long and the short + * code of the local settings and it will be insured that this will not add + * duplicates. So in an german local the setting "environment, de_DE, en, de" + * will result in "de_DE, de, en". + * + * Another special word is "none" which separates the preferred from all codes + * in this setting. So setting and method can be used to get codes the user want + * to see or to get all language codes APT (should) have Translations available. + * + * \param All return all codes or only codes for languages we want to use + * \param Cached saves the result so we need to calculated it only once + * this parameter should only be used for testing purposes. + * \param Locale don't get the locale from the system but use this one instead + * this parameter should only be used for testing purposes. + * + * \return a vector of (all) Language Codes in the preferred usage order + */ + std::vector<std::string> const getLanguages(bool const &All = false, + bool const &Cached = true, char const ** const Locale = 0); + + /** \brief Are we interested in the given Language? + * + * \param Lang is the language we want to check + * \param All defines if we check against all codes or only against used codes + * \return true if we are interested, false otherwise + */ + bool checkLanguage(std::string Lang, bool const All = false); + + /** \brief Returns a vector of Architectures we support + * + * \param Cached saves the result so we need to calculated it only once + * this parameter should only be used for testing purposes. + * + * \return a vector of Architectures in preferred order + */ + std::vector<std::string> const getArchitectures(bool const &Cached = true); + + /** \brief Are we interested in the given Architecture? + * + * \param Arch we want to check + * \return true if we are interested, false otherwise + */ + bool checkArchitecture(std::string const &Arch); + + /** \brief Representation of supported compressors */ + struct Compressor { + std::string Name; + std::string Extension; + std::string Binary; + std::vector<std::string> CompressArgs; + std::vector<std::string> UncompressArgs; + unsigned short Cost; + + Compressor(char const *name, char const *extension, char const *binary, + char const *compressArg, char const *uncompressArg, + unsigned short const cost); + Compressor() : Cost(std::numeric_limits<unsigned short>::max()) {}; + }; + + /** \brief Return a vector of Compressors supported for data.tar's + * + * \param Cached saves the result so we need to calculated it only once + * this parameter should only be used for testing purposes. + * + * \return a vector of Compressors + */ + std::vector<Compressor> const getCompressors(bool const Cached = true); + + /** \brief Return a vector of extensions supported for data.tar's */ + std::vector<std::string> const getCompressorExtensions(); + + /** \return Return a vector of enabled build profile specifications */ + std::vector<std::string> const getBuildProfiles(); + /** \return Return a comma-separated list of enabled build profile specifications */ + std::string const getBuildProfilesString(); + /*}}}*/ +} + /*}}}*/ +} +#endif diff --git a/apt-pkg/cachefile.cc b/apt-pkg/cachefile.cc new file mode 100644 index 0000000..9a1a6cf --- /dev/null +++ b/apt-pkg/cachefile.cc @@ -0,0 +1,363 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + CacheFile - Simple wrapper class for opening, generating and whatnot + + This class implements a simple 2 line mechanism to open various sorts + of caches. It can operate as root, as not root, show progress and so on, + it transparently handles everything necessary. + + ##################################################################### */ + /*}}}*/ +// 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/fileutl.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/mmap.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgcachegen.h> +#include <apt-pkg/pkgsystem.h> +#include <apt-pkg/policy.h> +#include <apt-pkg/progress.h> +#include <apt-pkg/sourcelist.h> + +#include <memory> +#include <string> +#include <vector> +#include <string.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ + +struct pkgCacheFile::Private +{ + bool WithLock = false; +}; + +// CacheFile::CacheFile - Constructor /*{{{*/ +pkgCacheFile::pkgCacheFile() : d(new Private()), ExternOwner(false), Map(NULL), Cache(NULL), + DCache(NULL), SrcList(NULL), Policy(NULL) +{ +} +pkgCacheFile::pkgCacheFile(pkgDepCache * const Owner) : d(new Private()), ExternOwner(true), + Map(&Owner->GetCache().GetMap()), Cache(&Owner->GetCache()), + DCache(Owner), SrcList(NULL), Policy(NULL) +{ +} + /*}}}*/ +// CacheFile::~CacheFile - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgCacheFile::~pkgCacheFile() +{ + if (ExternOwner == false) + { + delete DCache; + delete Cache; + delete Map; + } + delete Policy; + delete SrcList; + if (d->WithLock == true) + _system->UnLock(true); + + delete d; +} + /*}}}*/ +// CacheFile::BuildCaches - Open and build the cache files /*{{{*/ +class APT_HIDDEN ScopedErrorMerge { +public: + ScopedErrorMerge() { _error->PushToStack(); } + ~ScopedErrorMerge() { _error->MergeWithStack(); } +}; + +bool pkgCacheFile::BuildCaches(OpProgress *Progress, bool WithLock) +{ + std::unique_ptr<pkgCache> Cache; + std::unique_ptr<MMap> Map; + + if (this->Cache != NULL) + return true; + + ScopedErrorMerge sem; + if (_config->FindB("pkgCacheFile::Generate", true) == false) + { + FileFd file(_config->FindFile("Dir::Cache::pkgcache"), FileFd::ReadOnly); + if (file.IsOpen() == false || file.Failed()) + return false; + Map.reset(new MMap(file, MMap::Public|MMap::ReadOnly)); + if (unlikely(Map->validData() == false)) + return false; + Cache.reset(new pkgCache(Map.get())); + if (_error->PendingError() == true) + return false; + + this->Cache = Cache.release(); + this->Map = Map.release(); + return true; + } + + if (WithLock == true) + { + if (_system->Lock() == false) + return false; + d->WithLock = true; + } + + if (_error->PendingError() == true) + return false; + + if (BuildSourceList(Progress) == false) + return false; + + // Read the caches + MMap *TmpMap = nullptr; + pkgCache *TmpCache = nullptr; + bool Res = pkgCacheGenerator::MakeStatusCache(*SrcList,Progress,&TmpMap, &TmpCache, true); + Map.reset(TmpMap); + Cache.reset(TmpCache); + if (Progress != NULL) + Progress->Done(); + if (Res == false) + return _error->Error(_("The package lists or status file could not be parsed or opened.")); + + /* This sux, remove it someday */ + if (_error->PendingError() == true) + _error->Warning(_("You may want to run apt-get update to correct these problems")); + + if (Cache == nullptr) + Cache.reset(new pkgCache(Map.get())); + if (_error->PendingError() == true) + return false; + this->Map = Map.release(); + this->Cache = Cache.release(); + + return true; +} + /*}}}*/ +// CacheFile::BuildSourceList - Open and build all relevant sources.list/*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgCacheFile::BuildSourceList(OpProgress * /*Progress*/) +{ + std::unique_ptr<pkgSourceList> SrcList; + if (this->SrcList != NULL) + return true; + + SrcList.reset(new pkgSourceList()); + if (SrcList->ReadMainList() == false) + return _error->Error(_("The list of sources could not be read.")); + this->SrcList = SrcList.release(); + return true; +} + /*}}}*/ +// CacheFile::BuildPolicy - Open and build all relevant preferences /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgCacheFile::BuildPolicy(OpProgress * /*Progress*/) +{ + std::unique_ptr<pkgPolicy> Policy; + if (this->Policy != NULL) + return true; + + Policy.reset(new pkgPolicy(Cache)); + if (_error->PendingError() == true) + return false; + + ReadPinFile(*Policy); + ReadPinDir(*Policy); + + this->Policy = Policy.release(); + return _error->PendingError() == false; +} + /*}}}*/ +// CacheFile::BuildDepCache - Open and build the dependency cache /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgCacheFile::BuildDepCache(OpProgress *Progress) +{ + if (BuildCaches(Progress, false) == false) + return false; + + std::unique_ptr<pkgDepCache> DCache; + if (this->DCache != NULL) + return true; + + if (BuildPolicy(Progress) == false) + return false; + + DCache.reset(new pkgDepCache(Cache,Policy)); + if (_error->PendingError() == true) + return false; + if (DCache->Init(Progress) == false) + return false; + + this->DCache = DCache.release(); + return true; +} + /*}}}*/ +// CacheFile::Open - Open the cache files, creating if necessary /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgCacheFile::Open(OpProgress *Progress, bool WithLock) +{ + if (BuildCaches(Progress,WithLock) == false) + return false; + + if (BuildPolicy(Progress) == false) + return false; + + if (BuildDepCache(Progress) == false) + return false; + + if (Progress != NULL) + Progress->Done(); + if (_error->PendingError() == true) + return false; + + return true; +} + /*}}}*/ +bool pkgCacheFile::AddIndexFile(pkgIndexFile * const File) /*{{{*/ +{ + if (SrcList == NULL) + if (BuildSourceList() == false) + return false; + SrcList->AddVolatileFile(File); + + if (Cache == nullptr || File->HasPackages() == false || File->Exists() == false) + return true; + + if (File->FindInCache(*Cache).end() == false) + return _error->Warning("Duplicate sources.list entry %s", + File->Describe().c_str()); + + if (ExternOwner == false) + { + delete DCache; + delete Cache; + } + delete Policy; + DCache = NULL; + Policy = NULL; + Cache = NULL; + + if (ExternOwner == false) + { + // a dynamic mmap means that we have build at least parts of the cache + // in memory – which we might or might not have written to disk. + // Throwing away would therefore be a very costly operation we want to avoid + DynamicMMap * dynmmap = dynamic_cast<DynamicMMap*>(Map); + if (dynmmap != nullptr) + { + { + pkgCacheGenerator Gen(dynmmap, nullptr); + if (Gen.Start() == false || File->Merge(Gen, nullptr) == false) + return false; + } + Cache = new pkgCache(Map); + if (_error->PendingError() == true) { + delete Cache; + Cache = nullptr; + return false; + } + return true; + } + else + { + delete Map; + Map = NULL; + } + } + else + { + ExternOwner = false; + Map = NULL; + } + _system->UnLock(true); + return true; +} + /*}}}*/ +// CacheFile::RemoveCaches - remove all cache files from disk /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgCacheFile::RemoveCaches() +{ + std::string const pkgcache = _config->FindFile("Dir::cache::pkgcache"); + std::string const srcpkgcache = _config->FindFile("Dir::cache::srcpkgcache"); + + if (pkgcache.empty() == false && RealFileExists(pkgcache) == true) + RemoveFile("RemoveCaches", pkgcache); + if (srcpkgcache.empty() == false && RealFileExists(srcpkgcache) == true) + RemoveFile("RemoveCaches", srcpkgcache); + if (pkgcache.empty() == false) + { + std::string cachedir = flNotFile(pkgcache); + std::string cachefile = flNotDir(pkgcache); + if (cachedir.empty() != true && cachefile.empty() != true && DirectoryExists(cachedir) == true) + { + cachefile.append("."); + std::vector<std::string> caches = GetListOfFilesInDir(cachedir, false); + for (std::vector<std::string>::const_iterator file = caches.begin(); file != caches.end(); ++file) + { + std::string nuke = flNotDir(*file); + if (strncmp(cachefile.c_str(), nuke.c_str(), cachefile.length()) != 0) + continue; + RemoveFile("RemoveCaches", *file); + } + } + } + + if (srcpkgcache.empty() == true) + return; + + std::string cachedir = flNotFile(srcpkgcache); + std::string cachefile = flNotDir(srcpkgcache); + if (cachedir.empty() == true || cachefile.empty() == true || DirectoryExists(cachedir) == false) + return; + cachefile.append("."); + std::vector<std::string> caches = GetListOfFilesInDir(cachedir, false); + for (std::vector<std::string>::const_iterator file = caches.begin(); file != caches.end(); ++file) + { + std::string nuke = flNotDir(*file); + if (strncmp(cachefile.c_str(), nuke.c_str(), cachefile.length()) != 0) + continue; + RemoveFile("RemoveCaches", *file); + } +} + /*}}}*/ +// CacheFile::Close - close the cache files /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgCacheFile::Close() +{ + if (ExternOwner == false) + { + delete DCache; + delete Cache; + delete Map; + } + else + ExternOwner = false; + delete Policy; + delete SrcList; + if (d->WithLock == true) + { + _system->UnLock(true); + d->WithLock = false; + } + + Map = NULL; + DCache = NULL; + Policy = NULL; + Cache = NULL; + SrcList = NULL; +} + /*}}}*/ diff --git a/apt-pkg/cachefile.h b/apt-pkg/cachefile.h new file mode 100644 index 0000000..ed5d5c5 --- /dev/null +++ b/apt-pkg/cachefile.h @@ -0,0 +1,95 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + CacheFile - Simple wrapper class for opening, generating and whatnot + + This class implements a simple 2 line mechanism to open various sorts + of caches. It can operate as root, as not root, show progress and so on, + it transparently handles everything necessary. + + This means it can rebuild caches from the source list and instantiates + and prepares the standard policy mechanism. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_CACHEFILE_H +#define PKGLIB_CACHEFILE_H + +#include <stddef.h> + +#include <apt-pkg/depcache.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/acquire.h> +#include <apt-pkg/policy.h> +#include <apt-pkg/sourcelist.h> +#endif + +class MMap; +class pkgPolicy; +class pkgSourceList; +class pkgIndexFile; +class OpProgress; + +class pkgCacheFile +{ + struct Private; + /** \brief dpointer placeholder (for later in case we need it) */ + Private *const d; + bool ExternOwner; + + protected: + MMap *Map; + pkgCache *Cache; + pkgDepCache *DCache; + pkgSourceList *SrcList; + + public: + pkgPolicy *Policy; + + // We look pretty much exactly like a pointer to a dep cache + inline operator pkgCache &() const {return *Cache;}; + inline operator pkgCache *() const {return Cache;}; + inline operator pkgDepCache &() const {return *DCache;}; + inline operator pkgDepCache *() const {return DCache;}; + inline operator pkgPolicy &() const {return *Policy;}; + inline operator pkgPolicy *() const {return Policy;}; + inline operator pkgSourceList &() const {return *SrcList;}; + inline operator pkgSourceList *() const {return SrcList;}; + inline pkgDepCache *operator ->() const {return DCache;}; + inline pkgDepCache &operator *() const {return *DCache;}; + inline pkgDepCache::StateCache &operator [](pkgCache::PkgIterator const &I) const {return (*DCache)[I];}; + inline unsigned char &operator [](pkgCache::DepIterator const &I) const {return (*DCache)[I];}; + + bool BuildCaches(OpProgress *Progress = NULL,bool WithLock = true); + APT_DEPRECATED_MSG("Pass Progress in as a pointer") bool BuildCaches(OpProgress &Progress,bool const &WithLock = true) { return BuildCaches(&Progress, WithLock); }; + bool BuildSourceList(OpProgress *Progress = NULL); + bool BuildPolicy(OpProgress *Progress = NULL); + bool BuildDepCache(OpProgress *Progress = NULL); + bool Open(OpProgress *Progress = NULL, bool WithLock = true); + inline bool ReadOnlyOpen(OpProgress *Progress = NULL) { return Open(Progress, false); }; + APT_DEPRECATED_MSG("Pass Progress in as a pointer") bool Open(OpProgress &Progress,bool const &WithLock = true) { return Open(&Progress, WithLock); }; + static void RemoveCaches(); + void Close(); + + bool AddIndexFile(pkgIndexFile * const File); + + inline pkgCache* GetPkgCache() { BuildCaches(NULL, false); return Cache; }; + inline pkgDepCache* GetDepCache() { BuildDepCache(); return DCache; }; + inline pkgPolicy* GetPolicy() { BuildPolicy(); return Policy; }; + inline pkgSourceList* GetSourceList() { BuildSourceList(); return SrcList; }; + + inline bool IsPkgCacheBuilt() const { return (Cache != NULL); }; + inline bool IsDepCacheBuilt() const { return (DCache != NULL); }; + inline bool IsPolicyBuilt() const { return (Policy != NULL); }; + inline bool IsSrcListBuilt() const { return (SrcList != NULL); }; + + pkgCacheFile(); + explicit pkgCacheFile(pkgDepCache * const Owner); + virtual ~pkgCacheFile(); +}; + +#endif diff --git a/apt-pkg/cachefilter.cc b/apt-pkg/cachefilter.cc new file mode 100644 index 0000000..eadbb98 --- /dev/null +++ b/apt-pkg/cachefilter.cc @@ -0,0 +1,264 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/** \file cachefilter.h + Collection of functor classes */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cachefilter.h> +#include <apt-pkg/error.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/strutl.h> + +#include <algorithm> +#include <string> +#include <unordered_map> +#include <fnmatch.h> +#include <regex.h> +#include <string.h> + +#include <apti18n.h> + /*}}}*/ +namespace APT { + +APT_HIDDEN std::unordered_map<std::string, std::vector<std::string>> ArchToTupleMap; + +namespace CacheFilter { +Matcher::~Matcher() {} +PackageMatcher::~PackageMatcher() {} + +// Name matches RegEx /*{{{*/ +PackageNameMatchesRegEx::PackageNameMatchesRegEx(std::string const &Pattern) { + pattern = new regex_t; + int const Res = regcomp(pattern, Pattern.c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB); + if (Res == 0) + return; + + delete pattern; + pattern = NULL; + char Error[300]; + regerror(Res, pattern, Error, sizeof(Error)); + _error->Error(_("Regex compilation error - %s"), Error); +} +bool PackageNameMatchesRegEx::operator() (pkgCache::PkgIterator const &Pkg) { + if (unlikely(pattern == NULL)) + return false; + else + return regexec(pattern, Pkg.Name(), 0, 0, 0) == 0; +} +bool PackageNameMatchesRegEx::operator() (pkgCache::GrpIterator const &Grp) { + if (unlikely(pattern == NULL)) + return false; + else + return regexec(pattern, Grp.Name(), 0, 0, 0) == 0; +} +PackageNameMatchesRegEx::~PackageNameMatchesRegEx() { + if (pattern == NULL) + return; + regfree(pattern); + delete pattern; +} + /*}}}*/ +// Name matches Fnmatch /*{{{*/ +PackageNameMatchesFnmatch::PackageNameMatchesFnmatch(std::string const &Pattern) : + Pattern(Pattern) {} +bool PackageNameMatchesFnmatch::operator() (pkgCache::PkgIterator const &Pkg) { + return fnmatch(Pattern.c_str(), Pkg.Name(), FNM_CASEFOLD) == 0; +} +bool PackageNameMatchesFnmatch::operator() (pkgCache::GrpIterator const &Grp) { + return fnmatch(Pattern.c_str(), Grp.Name(), FNM_CASEFOLD) == 0; +} + /*}}}*/ +// Architecture matches <abi>-<libc>-<kernel>-<cpu> specification /*{{{*/ +//---------------------------------------------------------------------- + +static std::vector<std::string> ArchToTuple(std::string arch) { + // Strip leading linux- from arch if present + // dpkg says this may disappear in the future + if (APT::String::Startswith(arch, std::string("linux-"))) + arch = arch.substr(6); + + auto it = ArchToTupleMap.find(arch); + if (it != ArchToTupleMap.end()) + { + std::vector<std::string> result = it->second; + // Hack in support for triplets + if (result.size() == 3) + result.emplace(result.begin(), "base"); + return result; + } else + { + return {}; + } +} + +static std::vector<std::string> PatternToTuple(std::string const &arch) { + std::vector<std::string> tuple = VectorizeString(arch, '-'); + if (std::find(tuple.begin(), tuple.end(), std::string("any")) != tuple.end() || + std::find(arch.begin(), arch.end(), '*') != arch.end()) { + while (tuple.size() < 4) { + tuple.emplace(tuple.begin(), "any"); + } + return tuple; + } else + return ArchToTuple(arch); +} + +/* The complete architecture, consisting of <abi>-<libc>-<kernel>-<cpu>. */ +static std::string CompleteArch(std::string const &arch, bool const isPattern) { + auto tuple = isPattern ? PatternToTuple(arch) : ArchToTuple(arch); + + // Bah, the commandline will try and pass us stuff like amd64- -- we need + // that not to match an architecture, but the code below would turn it into + // a valid tuple. Let's just use an invalid tuple here. + if (APT::String::Endswith(arch, "-") || APT::String::Startswith(arch, "-")) + return "invalid-invalid-invalid-invalid"; + + if (tuple.empty()) { + // Fallback for unknown architectures + // Patterns never fail if they contain wildcards, so by this point, arch + // has no wildcards. + tuple = VectorizeString(arch, '-'); + switch (tuple.size()) { + case 1: + tuple.emplace(tuple.begin(), "linux"); + /* fall through */ + case 2: + tuple.emplace(tuple.begin(), "gnu"); + /* fall through */ + case 3: + tuple.emplace(tuple.begin(), "base"); + /* fall through */ + break; + } + } + + std::replace(tuple.begin(), tuple.end(), std::string("any"), std::string("*")); + return APT::String::Join(tuple, "-"); +} +PackageArchitectureMatchesSpecification::PackageArchitectureMatchesSpecification(std::string const &pattern, bool const pisPattern) : + literal(pattern), complete(CompleteArch(pattern, pisPattern)), isPattern(pisPattern) { +} +bool PackageArchitectureMatchesSpecification::operator() (char const * const &arch) { + if (strcmp(literal.c_str(), arch) == 0 || + strcmp(complete.c_str(), arch) == 0) + return true; + std::string const pkgarch = CompleteArch(arch, !isPattern); + if (isPattern == true) + return fnmatch(complete.c_str(), pkgarch.c_str(), 0) == 0; + return fnmatch(pkgarch.c_str(), complete.c_str(), 0) == 0; +} +bool PackageArchitectureMatchesSpecification::operator() (pkgCache::PkgIterator const &Pkg) { + return (*this)(Pkg.Arch()); +} +PackageArchitectureMatchesSpecification::~PackageArchitectureMatchesSpecification() { +} + /*}}}*/ +// Package is new install /*{{{*/ +PackageIsNewInstall::PackageIsNewInstall(pkgCacheFile * const Cache) : Cache(Cache) {} +APT_PURE bool PackageIsNewInstall::operator() (pkgCache::PkgIterator const &Pkg) { + return (*Cache)[Pkg].NewInstall(); +} +PackageIsNewInstall::~PackageIsNewInstall() {} + /*}}}*/ +// Generica like True, False, NOT, AND, OR /*{{{*/ +APT_PURE bool TrueMatcher::operator() (pkgCache::PkgIterator const &) { return true; } +APT_PURE bool TrueMatcher::operator() (pkgCache::GrpIterator const &) { return true; } +APT_PURE bool TrueMatcher::operator() (pkgCache::VerIterator const &) { return true; } + +APT_PURE bool FalseMatcher::operator() (pkgCache::PkgIterator const &) { return false; } +APT_PURE bool FalseMatcher::operator() (pkgCache::GrpIterator const &) { return false; } +APT_PURE bool FalseMatcher::operator() (pkgCache::VerIterator const &) { return false; } + +NOTMatcher::NOTMatcher(Matcher * const matcher) : matcher(matcher) {} +bool NOTMatcher::operator() (pkgCache::PkgIterator const &Pkg) { return ! (*matcher)(Pkg); } +bool NOTMatcher::operator() (pkgCache::GrpIterator const &Grp) { return ! (*matcher)(Grp); } +bool NOTMatcher::operator() (pkgCache::VerIterator const &Ver) { return ! (*matcher)(Ver); } +NOTMatcher::~NOTMatcher() { delete matcher; } + +ANDMatcher::ANDMatcher() {} +ANDMatcher::ANDMatcher(Matcher * const matcher1) { + AND(matcher1); +} +ANDMatcher::ANDMatcher(Matcher * const matcher1, Matcher * const matcher2) { + AND(matcher1).AND(matcher2); +} +ANDMatcher::ANDMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3) { + AND(matcher1).AND(matcher2).AND(matcher3); +} +ANDMatcher::ANDMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3, Matcher * const matcher4) { + AND(matcher1).AND(matcher2).AND(matcher3).AND(matcher4); +} +ANDMatcher::ANDMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3, Matcher * const matcher4, Matcher * const matcher5) { + AND(matcher1).AND(matcher2).AND(matcher3).AND(matcher4).AND(matcher5); +} +ANDMatcher& ANDMatcher::AND(Matcher * const matcher) { matchers.push_back(matcher); return *this; } +bool ANDMatcher::operator() (pkgCache::PkgIterator const &Pkg) { + for (std::vector<Matcher *>::const_iterator M = matchers.begin(); M != matchers.end(); ++M) + if ((**M)(Pkg) == false) + return false; + return true; +} +bool ANDMatcher::operator() (pkgCache::GrpIterator const &Grp) { + for (std::vector<Matcher *>::const_iterator M = matchers.begin(); M != matchers.end(); ++M) + if ((**M)(Grp) == false) + return false; + return true; +} +bool ANDMatcher::operator() (pkgCache::VerIterator const &Ver) { + for (std::vector<Matcher *>::const_iterator M = matchers.begin(); M != matchers.end(); ++M) + if ((**M)(Ver) == false) + return false; + return true; +} +ANDMatcher::~ANDMatcher() { + for (std::vector<Matcher *>::iterator M = matchers.begin(); M != matchers.end(); ++M) + delete *M; +} + +ORMatcher::ORMatcher() {} +ORMatcher::ORMatcher(Matcher * const matcher1) { + OR(matcher1); +} +ORMatcher::ORMatcher(Matcher * const matcher1, Matcher * const matcher2) { + OR(matcher1).OR(matcher2); +} +ORMatcher::ORMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3) { + OR(matcher1).OR(matcher2).OR(matcher3); +} +ORMatcher::ORMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3, Matcher * const matcher4) { + OR(matcher1).OR(matcher2).OR(matcher3).OR(matcher4); +} +ORMatcher::ORMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3, Matcher * const matcher4, Matcher * const matcher5) { + OR(matcher1).OR(matcher2).OR(matcher3).OR(matcher4).OR(matcher5); +} +ORMatcher& ORMatcher::OR(Matcher * const matcher) { matchers.push_back(matcher); return *this; } +bool ORMatcher::operator() (pkgCache::PkgIterator const &Pkg) { + for (std::vector<Matcher *>::const_iterator M = matchers.begin(); M != matchers.end(); ++M) + if ((**M)(Pkg) == true) + return true; + return false; +} +bool ORMatcher::operator() (pkgCache::GrpIterator const &Grp) { + for (std::vector<Matcher *>::const_iterator M = matchers.begin(); M != matchers.end(); ++M) + if ((**M)(Grp) == true) + return true; + return false; +} +bool ORMatcher::operator() (pkgCache::VerIterator const &Ver) { + for (std::vector<Matcher *>::const_iterator M = matchers.begin(); M != matchers.end(); ++M) + if ((**M)(Ver) == true) + return true; + return false; +} +ORMatcher::~ORMatcher() { + for (std::vector<Matcher *>::iterator M = matchers.begin(); M != matchers.end(); ++M) + delete *M; +} + /*}}}*/ + +} +} diff --git a/apt-pkg/cachefilter.h b/apt-pkg/cachefilter.h new file mode 100644 index 0000000..8a6c013 --- /dev/null +++ b/apt-pkg/cachefilter.h @@ -0,0 +1,150 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/** \file cachefilter.h + Collection of functor classes */ + /*}}}*/ +#ifndef APT_CACHEFILTER_H +#define APT_CACHEFILTER_H +// Include Files /*{{{*/ +#include <apt-pkg/pkgcache.h> + +#include <string> +#include <vector> + +#include <regex.h> + +class pkgCacheFile; + /*}}}*/ +namespace APT { +namespace CacheFilter { + +class Matcher { +public: + virtual bool operator() (pkgCache::PkgIterator const &/*Pkg*/) = 0; + virtual bool operator() (pkgCache::GrpIterator const &/*Grp*/) = 0; + virtual bool operator() (pkgCache::VerIterator const &/*Ver*/) = 0; + virtual ~Matcher(); +}; + +class PackageMatcher : public Matcher { +public: + virtual bool operator() (pkgCache::PkgIterator const &Pkg) APT_OVERRIDE = 0; + virtual bool operator() (pkgCache::VerIterator const &Ver) APT_OVERRIDE { return (*this)(Ver.ParentPkg()); } + virtual bool operator() (pkgCache::GrpIterator const &/*Grp*/) APT_OVERRIDE { return false; } + virtual ~PackageMatcher(); +}; + +// Generica like True, False, NOT, AND, OR /*{{{*/ +class TrueMatcher : public Matcher { +public: + virtual bool operator() (pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual bool operator() (pkgCache::GrpIterator const &Grp) APT_OVERRIDE; + virtual bool operator() (pkgCache::VerIterator const &Ver) APT_OVERRIDE; +}; + +class FalseMatcher : public Matcher { +public: + virtual bool operator() (pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual bool operator() (pkgCache::GrpIterator const &Grp) APT_OVERRIDE; + virtual bool operator() (pkgCache::VerIterator const &Ver) APT_OVERRIDE; +}; + +class NOTMatcher : public Matcher { + Matcher * const matcher; +public: + explicit NOTMatcher(Matcher * const matcher); + virtual bool operator() (pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual bool operator() (pkgCache::GrpIterator const &Grp) APT_OVERRIDE; + virtual bool operator() (pkgCache::VerIterator const &Ver) APT_OVERRIDE; + virtual ~NOTMatcher(); +}; + +class ANDMatcher : public Matcher { + std::vector<Matcher *> matchers; +public: + // 5 ought to be enough for everybody… c++11 variadic templates would be nice + ANDMatcher(); + explicit ANDMatcher(Matcher * const matcher1); + ANDMatcher(Matcher * const matcher1, Matcher * const matcher2); + ANDMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3); + ANDMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3, Matcher * const matcher4); + ANDMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3, Matcher * const matcher4, Matcher * const matcher5); + ANDMatcher& AND(Matcher * const matcher); + virtual bool operator() (pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual bool operator() (pkgCache::GrpIterator const &Grp) APT_OVERRIDE; + virtual bool operator() (pkgCache::VerIterator const &Ver) APT_OVERRIDE; + virtual ~ANDMatcher(); +}; +class ORMatcher : public Matcher { + std::vector<Matcher *> matchers; +public: + // 5 ought to be enough for everybody… c++11 variadic templates would be nice + ORMatcher(); + explicit ORMatcher(Matcher * const matcher1); + ORMatcher(Matcher * const matcher1, Matcher * const matcher2); + ORMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3); + ORMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3, Matcher * const matcher4); + ORMatcher(Matcher * const matcher1, Matcher * const matcher2, Matcher * const matcher3, Matcher * const matcher4, Matcher * const matcher5); + ORMatcher& OR(Matcher * const matcher); + virtual bool operator() (pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual bool operator() (pkgCache::GrpIterator const &Grp) APT_OVERRIDE; + virtual bool operator() (pkgCache::VerIterator const &Ver) APT_OVERRIDE; + virtual ~ORMatcher(); +}; + /*}}}*/ +class PackageNameMatchesRegEx : public PackageMatcher { /*{{{*/ + regex_t* pattern; +public: + explicit PackageNameMatchesRegEx(std::string const &Pattern); + virtual bool operator() (pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual bool operator() (pkgCache::GrpIterator const &Grp) APT_OVERRIDE; + virtual ~PackageNameMatchesRegEx(); +}; + /*}}}*/ +class PackageNameMatchesFnmatch : public PackageMatcher { /*{{{*/ + const std::string Pattern; +public: + explicit PackageNameMatchesFnmatch(std::string const &Pattern); + virtual bool operator() (pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual bool operator() (pkgCache::GrpIterator const &Grp) APT_OVERRIDE; + virtual ~PackageNameMatchesFnmatch() {}; +}; + /*}}}*/ +class PackageArchitectureMatchesSpecification : public PackageMatcher { /*{{{*/ +/** \class PackageArchitectureMatchesSpecification + \brief matching against architecture specification strings + + The strings are of the format <libc>-<kernel>-<cpu> where either component, + or the whole string, can be the wildcard "any" as defined in + debian-policy §11.1 "Architecture specification strings". + + Examples: i386, mipsel, musl-linux-amd64, linux-any, any-amd64, any */ + std::string literal; + std::string complete; + bool isPattern; +public: + /** \brief matching against architecture specification strings + * + * @param pattern is the architecture specification string + * @param isPattern defines if the given \b pattern is a + * architecture specification pattern to match others against + * or if it is the fixed string and matched against patterns + */ + PackageArchitectureMatchesSpecification(std::string const &pattern, bool const isPattern = true); + bool operator() (char const * const &arch); + virtual bool operator() (pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual ~PackageArchitectureMatchesSpecification(); +}; + /*}}}*/ +class PackageIsNewInstall : public PackageMatcher { /*{{{*/ + pkgCacheFile * const Cache; +public: + explicit PackageIsNewInstall(pkgCacheFile * const Cache); + virtual bool operator() (pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual ~PackageIsNewInstall(); +}; + /*}}}*/ + +} +} +#endif diff --git a/apt-pkg/cacheiterators.h b/apt-pkg/cacheiterators.h new file mode 100644 index 0000000..2085306 --- /dev/null +++ b/apt-pkg/cacheiterators.h @@ -0,0 +1,531 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Cache Iterators - Iterators for navigating the cache structure + + The iterators all provides ++,==,!=,->,* and end for their type. + The end function can be used to tell if the list has been fully + traversed. + + Unlike STL iterators these contain helper functions to access the data + that is being iterated over. This is because the data structures can't + be formed in a manner that is intuitive to use and also mmapable. + + For each variable in the target structure that would need a translation + to be accessed correctly a translating function of the same name is + present in the iterator. If applicable the translating function will + return an iterator. + + The DepIterator can iterate over two lists, a list of 'version depends' + or a list of 'package reverse depends'. The type is determined by the + structure passed to the constructor, which should be the structure + that has the depends pointer as a member. The provide iterator has the + same system. + + This header is not user includable, please use apt-pkg/pkgcache.h + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_CACHEITERATORS_H +#define PKGLIB_CACHEITERATORS_H +#ifndef __PKGLIB_IN_PKGCACHE_H +#warning apt-pkg/cacheiterators.h should not be included directly, include apt-pkg/pkgcache.h instead +#endif +#include <apt-pkg/macros.h> + +#include <iosfwd> +#include <iterator> +#include <string> +#ifdef APT_PKG_EXPOSE_STRING_VIEW +#include <apt-pkg/string_view.h> +#endif + +#include <string.h> + +// abstract Iterator template /*{{{*/ +/* This template provides the very basic iterator methods we + need to have for doing some walk-over-the-cache magic */ +template<typename Str, typename Itr> class pkgCache::Iterator : + public std::iterator<std::forward_iterator_tag, Str> { + /** \brief Returns the Pointer for this struct in the owner + * The implementation of this method should be pretty short + * as it will only return the Pointer into the mmap stored + * in the owner but the name of this pointer is different for + * each structure and we want to abstract here at least for the + * basic methods from the actual structure. + * \return Pointer to the first structure of this type + */ + Str* OwnerPointer() const { return static_cast<Itr const*>(this)->OwnerPointer(); } + + protected: + Str *S; + pkgCache *Owner; + + public: + // Iteration + inline bool end() const {return Owner == 0 || S == OwnerPointer();} + + // Comparison + inline bool operator ==(const Itr &B) const {return S == B.S;} + inline bool operator !=(const Itr &B) const {return S != B.S;} + + // Accessors + inline Str *operator ->() {return S;} + inline Str const *operator ->() const {return S;} + inline operator Str *() {return S == OwnerPointer() ? 0 : S;} + inline operator Str const *() const {return S == OwnerPointer() ? 0 : S;} + inline Str &operator *() {return *S;} + inline Str const &operator *() const {return *S;} + inline pkgCache *Cache() const {return Owner;} + + // Mixed stuff + inline bool IsGood() const { return S && Owner && ! end();} + inline unsigned long Index() const {return S - OwnerPointer();} + + void ReMap(void const * const oldMap, void const * const newMap) { + if (Owner == 0 || S == 0) + return; + S += static_cast<Str const *>(newMap) - static_cast<Str const *>(oldMap); + } + + // Constructors - look out for the variable assigning + inline Iterator() : S(0), Owner(0) {} + inline Iterator(pkgCache &Owner,Str *T = 0) : S(T), Owner(&Owner) {} +}; + /*}}}*/ +// Group Iterator /*{{{*/ +/* Packages with the same name are collected in a Group so someone only + interest in package names can iterate easily over the names, so the + different architectures can be treated as of the "same" package + (apt internally treat them as totally different packages) */ +class pkgCache::GrpIterator: public Iterator<Group, GrpIterator> { + long HashIndex; + + public: + inline Group* OwnerPointer() const { + return (Owner != 0) ? Owner->GrpP : 0; + } + + // This constructor is the 'begin' constructor, never use it. + explicit inline GrpIterator(pkgCache &Owner) : Iterator<Group, GrpIterator>(Owner), HashIndex(-1) { + S = OwnerPointer(); + operator++(); + } + + GrpIterator& operator++(); + inline GrpIterator operator++(int) { GrpIterator const tmp(*this); operator++(); return tmp; } + + inline const char *Name() const {return S->Name == 0?0:Owner->StrP + S->Name;} + inline PkgIterator PackageList() const; + PkgIterator FindPkg(std::string Arch = "any") const; +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN PkgIterator FindPkg(APT::StringView Arch = APT::StringView("any", 3)) const; + APT_HIDDEN PkgIterator FindPkg(const char *Arch) const; +#endif + /** \brief find the package with the "best" architecture + + The best architecture is either the "native" or the first + in the list of Architectures which is not an end-Pointer + + \param PreferNonVirtual tries to respond with a non-virtual package + and only if this fails returns the best virtual package */ + PkgIterator FindPreferredPkg(bool const &PreferNonVirtual = true) const; + PkgIterator NextPkg(PkgIterator const &Pkg) const; + + // Constructors + inline GrpIterator(pkgCache &Owner, Group *Trg) : Iterator<Group, GrpIterator>(Owner, Trg), HashIndex(0) { + if (S == 0) + S = OwnerPointer(); + } + inline GrpIterator() : Iterator<Group, GrpIterator>(), HashIndex(0) {} + +}; + /*}}}*/ +// Package Iterator /*{{{*/ +class pkgCache::PkgIterator: public Iterator<Package, PkgIterator> { + long HashIndex; + + public: + inline Package* OwnerPointer() const { + return (Owner != 0) ? Owner->PkgP : 0; + } + + // This constructor is the 'begin' constructor, never use it. + explicit inline PkgIterator(pkgCache &Owner) : Iterator<Package, PkgIterator>(Owner), HashIndex(-1) { + S = OwnerPointer(); + operator++(); + } + + PkgIterator& operator++(); + inline PkgIterator operator++(int) { PkgIterator const tmp(*this); operator++(); return tmp; } + + enum OkState {NeedsNothing,NeedsUnpack,NeedsConfigure}; + + // Accessors + inline const char *Name() const { return Group().Name(); } + // Versions have sections - and packages can have different versions with different sections + // so this interface is broken by design. Run as fast as you can to Version.Section(). + APT_DEPRECATED_MSG("Use the .Section method of VerIterator instead") inline const char *Section() const; + inline bool Purge() const {return S->CurrentState == pkgCache::State::Purge || + (S->CurrentVer == 0 && S->CurrentState == pkgCache::State::NotInstalled);} + inline const char *Arch() const {return S->Arch == 0?0:Owner->StrP + S->Arch;} + inline APT_PURE GrpIterator Group() const { return GrpIterator(*Owner, Owner->GrpP + S->Group);} + + inline VerIterator VersionList() const APT_PURE; + inline VerIterator CurrentVer() const APT_PURE; + inline DepIterator RevDependsList() const APT_PURE; + inline PrvIterator ProvidesList() const APT_PURE; + OkState State() const APT_PURE; + APT_DEPRECATED_MSG("This method does not respect apt_preferences! Use pkgDepCache::GetCandidateVersion(Pkg)") const char *CandVersion() const APT_PURE; + const char *CurVersion() const APT_PURE; + + //Nice printable representation + APT_DEPRECATED_MSG("Use APT::PrettyPkg instead") friend std::ostream& operator <<(std::ostream& out, PkgIterator i); + std::string FullName(bool const &Pretty = false) const; + + // Constructors + inline PkgIterator(pkgCache &Owner,Package *Trg) : Iterator<Package, PkgIterator>(Owner, Trg), HashIndex(0) { + if (S == 0) + S = OwnerPointer(); + } + inline PkgIterator() : Iterator<Package, PkgIterator>(), HashIndex(0) {} +}; + /*}}}*/ +// Version Iterator /*{{{*/ +class pkgCache::VerIterator : public Iterator<Version, VerIterator> { + public: + inline Version* OwnerPointer() const { + return (Owner != 0) ? Owner->VerP : 0; + } + + // Iteration + inline VerIterator& operator++() {if (S != Owner->VerP) S = Owner->VerP + S->NextVer; return *this;} + inline VerIterator operator++(int) { VerIterator const tmp(*this); operator++(); return tmp; } + + // Comparison + int CompareVer(const VerIterator &B) const; + /** \brief compares two version and returns if they are similar + + This method should be used to identify if two pseudo versions are + referring to the same "real" version */ + inline bool SimilarVer(const VerIterator &B) const { + return (B.end() == false && S->Hash == B->Hash && strcmp(VerStr(), B.VerStr()) == 0); + } + + // Accessors + inline const char *VerStr() const {return S->VerStr == 0?0:Owner->StrP + S->VerStr;} + inline const char *Section() const {return S->Section == 0?0:Owner->StrP + S->Section;} + /** \brief source package name this version comes from + Always contains the name, even if it is the same as the binary name */ + inline const char *SourcePkgName() const {return Owner->StrP + S->SourcePkgName;} + /** \brief source version this version comes from + Always contains the version string, even if it is the same as the binary version */ + inline const char *SourceVerStr() const {return Owner->StrP + S->SourceVerStr;} + inline const char *Arch() const { + if ((S->MultiArch & pkgCache::Version::All) == pkgCache::Version::All) + return "all"; + return S->ParentPkg == 0?0:Owner->StrP + ParentPkg()->Arch; + } + inline PkgIterator ParentPkg() const {return PkgIterator(*Owner,Owner->PkgP + S->ParentPkg);} + + inline DescIterator DescriptionList() const; + DescIterator TranslatedDescription() const; + inline DepIterator DependsList() const; + inline PrvIterator ProvidesList() const; + inline VerFileIterator FileList() const; + bool Downloadable() const; + inline const char *PriorityType() const {return Owner->Priority(S->Priority);} + const char *MultiArchType() const APT_PURE; + std::string RelStr() const; + + bool Automatic() const; + VerFileIterator NewestFile() const; + + inline VerIterator(pkgCache &Owner,Version *Trg = 0) : Iterator<Version, VerIterator>(Owner, Trg) { + if (S == 0) + S = OwnerPointer(); + } + inline VerIterator() : Iterator<Version, VerIterator>() {} +}; + /*}}}*/ +// Description Iterator /*{{{*/ +class pkgCache::DescIterator : public Iterator<Description, DescIterator> { + public: + inline Description* OwnerPointer() const { + return (Owner != 0) ? Owner->DescP : 0; + } + + // Iteration + inline DescIterator& operator++() {if (S != Owner->DescP) S = Owner->DescP + S->NextDesc; return *this;} + inline DescIterator operator++(int) { DescIterator const tmp(*this); operator++(); return tmp; } + + // Comparison + int CompareDesc(const DescIterator &B) const; + + // Accessors + inline const char *LanguageCode() const {return Owner->StrP + S->language_code;} + inline const char *md5() const {return Owner->StrP + S->md5sum;} + inline DescFileIterator FileList() const; + + inline DescIterator() : Iterator<Description, DescIterator>() {} + inline DescIterator(pkgCache &Owner,Description *Trg = 0) : Iterator<Description, DescIterator>(Owner, Trg) { + if (S == 0) + S = Owner.DescP; + } +}; + /*}}}*/ +// Dependency iterator /*{{{*/ +class pkgCache::DepIterator : public Iterator<Dependency, DepIterator> { + enum {DepVer, DepRev} Type; + DependencyData * S2; + + public: + inline Dependency* OwnerPointer() const { + return (Owner != 0) ? Owner->DepP : 0; + } + + // Iteration + DepIterator& operator++(); + inline DepIterator operator++(int) { DepIterator const tmp(*this); operator++(); return tmp; } + + // Accessors + inline const char *TargetVer() const {return S2->Version == 0?0:Owner->StrP + S2->Version;} + inline PkgIterator TargetPkg() const {return PkgIterator(*Owner,Owner->PkgP + S2->Package);} + inline PkgIterator SmartTargetPkg() const {PkgIterator R(*Owner,0);SmartTargetPkg(R);return R;} + inline VerIterator ParentVer() const {return VerIterator(*Owner,Owner->VerP + S->ParentVer);} + inline PkgIterator ParentPkg() const {return PkgIterator(*Owner,Owner->PkgP + Owner->VerP[S->ParentVer].ParentPkg);} + inline bool Reverse() const {return Type == DepRev;} + bool IsCritical() const APT_PURE; + bool IsNegative() const APT_PURE; + bool IsIgnorable(PrvIterator const &Prv) const APT_PURE; + bool IsIgnorable(PkgIterator const &Pkg) const APT_PURE; + /* MultiArch can be translated to SingleArch for an resolver and we did so, + by adding dependencies to help the resolver understand the problem, but + sometimes it is needed to identify these to ignore them… */ + inline bool IsMultiArchImplicit() const APT_PURE { + return (S2->CompareOp & pkgCache::Dep::MultiArchImplicit) == pkgCache::Dep::MultiArchImplicit; + } + /* This covers additionally negative dependencies, which aren't arch-specific, + but change architecture nonetheless as a Conflicts: foo does applies for all archs */ + bool IsImplicit() const APT_PURE; + + bool IsSatisfied(VerIterator const &Ver) const APT_PURE; + bool IsSatisfied(PrvIterator const &Prv) const APT_PURE; + void GlobOr(DepIterator &Start,DepIterator &End); + Version **AllTargets() const; + bool SmartTargetPkg(PkgIterator &Result) const; + inline const char *CompType() const {return Owner->CompType(S2->CompareOp);} + inline const char *DepType() const {return Owner->DepType(S2->Type);} + + // overrides because we are special + struct DependencyProxy + { + map_stringitem_t &Version; + map_pointer_t &Package; + map_id_t &ID; + unsigned char &Type; + unsigned char &CompareOp; + map_pointer_t &ParentVer; + map_pointer_t &DependencyData; + map_pointer_t &NextRevDepends; + map_pointer_t &NextDepends; + map_pointer_t &NextData; + DependencyProxy const * operator->() const { return this; } + DependencyProxy * operator->() { return this; } + }; + inline DependencyProxy operator->() const {return (DependencyProxy) { S2->Version, S2->Package, S->ID, S2->Type, S2->CompareOp, S->ParentVer, S->DependencyData, S->NextRevDepends, S->NextDepends, S2->NextData };} + inline DependencyProxy operator->() {return (DependencyProxy) { S2->Version, S2->Package, S->ID, S2->Type, S2->CompareOp, S->ParentVer, S->DependencyData, S->NextRevDepends, S->NextDepends, S2->NextData };} + void ReMap(void const * const oldMap, void const * const newMap) + { + Iterator<Dependency, DepIterator>::ReMap(oldMap, newMap); + if (Owner == 0 || S == 0 || S2 == 0) + return; + S2 += static_cast<DependencyData const *>(newMap) - static_cast<DependencyData const *>(oldMap); + } + + //Nice printable representation + APT_DEPRECATED_MSG("Use APT::PrettyDep instead") friend std::ostream& operator <<(std::ostream& out, DepIterator D); + + inline DepIterator(pkgCache &Owner, Dependency *Trg, Version* = 0) : + Iterator<Dependency, DepIterator>(Owner, Trg), Type(DepVer), S2(Trg == 0 ? Owner.DepDataP : (Owner.DepDataP + Trg->DependencyData)) { + if (S == 0) + S = Owner.DepP; + } + inline DepIterator(pkgCache &Owner, Dependency *Trg, Package*) : + Iterator<Dependency, DepIterator>(Owner, Trg), Type(DepRev), S2(Trg == 0 ? Owner.DepDataP : (Owner.DepDataP + Trg->DependencyData)) { + if (S == 0) + S = Owner.DepP; + } + inline DepIterator() : Iterator<Dependency, DepIterator>(), Type(DepVer), S2(0) {} +}; + /*}}}*/ +// Provides iterator /*{{{*/ +class pkgCache::PrvIterator : public Iterator<Provides, PrvIterator> { + enum {PrvVer, PrvPkg} Type; + + public: + inline Provides* OwnerPointer() const { + return (Owner != 0) ? Owner->ProvideP : 0; + } + + // Iteration + inline PrvIterator& operator ++() {if (S != Owner->ProvideP) S = Owner->ProvideP + + (Type == PrvVer?S->NextPkgProv:S->NextProvides); return *this;} + inline PrvIterator operator++(int) { PrvIterator const tmp(*this); operator++(); return tmp; } + + // Accessors + inline const char *Name() const {return ParentPkg().Name();} + inline const char *ProvideVersion() const {return S->ProvideVersion == 0?0:Owner->StrP + S->ProvideVersion;} + inline PkgIterator ParentPkg() const {return PkgIterator(*Owner,Owner->PkgP + S->ParentPkg);} + inline VerIterator OwnerVer() const {return VerIterator(*Owner,Owner->VerP + S->Version);} + inline PkgIterator OwnerPkg() const {return PkgIterator(*Owner,Owner->PkgP + Owner->VerP[S->Version].ParentPkg);} + + /* MultiArch can be translated to SingleArch for an resolver and we did so, + by adding provides to help the resolver understand the problem, but + sometimes it is needed to identify these to ignore them… */ + bool IsMultiArchImplicit() const APT_PURE + { return (S->Flags & pkgCache::Flag::MultiArchImplicit) == pkgCache::Flag::MultiArchImplicit; } + + + inline PrvIterator() : Iterator<Provides, PrvIterator>(), Type(PrvVer) {} + inline PrvIterator(pkgCache &Owner, Provides *Trg, Version*) : + Iterator<Provides, PrvIterator>(Owner, Trg), Type(PrvVer) { + if (S == 0) + S = Owner.ProvideP; + } + inline PrvIterator(pkgCache &Owner, Provides *Trg, Package*) : + Iterator<Provides, PrvIterator>(Owner, Trg), Type(PrvPkg) { + if (S == 0) + S = Owner.ProvideP; + } +}; + /*}}}*/ +// Release file /*{{{*/ +class pkgCache::RlsFileIterator : public Iterator<ReleaseFile, RlsFileIterator> { + public: + inline ReleaseFile* OwnerPointer() const { + return (Owner != 0) ? Owner->RlsFileP : 0; + } + + // Iteration + inline RlsFileIterator& operator++() {if (S != Owner->RlsFileP) S = Owner->RlsFileP + S->NextFile;return *this;} + inline RlsFileIterator operator++(int) { RlsFileIterator const tmp(*this); operator++(); return tmp; } + + // Accessors + inline const char *FileName() const {return S->FileName == 0?0:Owner->StrP + S->FileName;} + inline const char *Archive() const {return S->Archive == 0?0:Owner->StrP + S->Archive;} + inline const char *Version() const {return S->Version == 0?0:Owner->StrP + S->Version;} + inline const char *Origin() const {return S->Origin == 0?0:Owner->StrP + S->Origin;} + inline const char *Codename() const {return S->Codename ==0?0:Owner->StrP + S->Codename;} + inline const char *Label() const {return S->Label == 0?0:Owner->StrP + S->Label;} + inline const char *Site() const {return S->Site == 0?0:Owner->StrP + S->Site;} + inline bool Flagged(pkgCache::Flag::ReleaseFileFlags const flag) const {return (S->Flags & flag) == flag; } + + APT_DEPRECATED_MSG("Can be remove without replacement; it is a no-op") + bool IsOk(); + std::string RelStr(); + + // Constructors + inline RlsFileIterator() : Iterator<ReleaseFile, RlsFileIterator>() {} + explicit inline RlsFileIterator(pkgCache &Owner) : Iterator<ReleaseFile, RlsFileIterator>(Owner, Owner.RlsFileP) {} + inline RlsFileIterator(pkgCache &Owner,ReleaseFile *Trg) : Iterator<ReleaseFile, RlsFileIterator>(Owner, Trg) {} +}; + /*}}}*/ +// Package file /*{{{*/ +class pkgCache::PkgFileIterator : public Iterator<PackageFile, PkgFileIterator> { + public: + inline PackageFile* OwnerPointer() const { + return (Owner != 0) ? Owner->PkgFileP : 0; + } + + // Iteration + inline PkgFileIterator& operator++() {if (S != Owner->PkgFileP) S = Owner->PkgFileP + S->NextFile; return *this;} + inline PkgFileIterator operator++(int) { PkgFileIterator const tmp(*this); operator++(); return tmp; } + + // Accessors + inline const char *FileName() const {return S->FileName == 0?0:Owner->StrP + S->FileName;} + inline pkgCache::RlsFileIterator ReleaseFile() const {return RlsFileIterator(*Owner, Owner->RlsFileP + S->Release);} + inline const char *Archive() const {return S->Release == 0 ? Component() : ReleaseFile().Archive();} + inline const char *Version() const {return S->Release == 0 ? NULL : ReleaseFile().Version();} + inline const char *Origin() const {return S->Release == 0 ? NULL : ReleaseFile().Origin();} + inline const char *Codename() const {return S->Release == 0 ? NULL : ReleaseFile().Codename();} + inline const char *Label() const {return S->Release == 0 ? NULL : ReleaseFile().Label();} + inline const char *Site() const {return S->Release == 0 ? NULL : ReleaseFile().Site();} + inline bool Flagged(pkgCache::Flag::ReleaseFileFlags const flag) const {return S->Release== 0 ? false : ReleaseFile().Flagged(flag);} + inline bool Flagged(pkgCache::Flag::PkgFFlags const flag) const {return (S->Flags & flag) == flag;} + inline const char *Component() const {return S->Component == 0?0:Owner->StrP + S->Component;} + inline const char *Architecture() const {return S->Architecture == 0?0:Owner->StrP + S->Architecture;} + inline const char *IndexType() const {return S->IndexType == 0?0:Owner->StrP + S->IndexType;} + + APT_DEPRECATED_MSG("Can be remove without replacement; it is a no-op") + bool IsOk(); + std::string RelStr(); + + // Constructors + inline PkgFileIterator() : Iterator<PackageFile, PkgFileIterator>() {} + explicit inline PkgFileIterator(pkgCache &Owner) : Iterator<PackageFile, PkgFileIterator>(Owner, Owner.PkgFileP) {} + inline PkgFileIterator(pkgCache &Owner,PackageFile *Trg) : Iterator<PackageFile, PkgFileIterator>(Owner, Trg) {} +}; + /*}}}*/ +// Version File /*{{{*/ +class pkgCache::VerFileIterator : public pkgCache::Iterator<VerFile, VerFileIterator> { + public: + inline VerFile* OwnerPointer() const { + return (Owner != 0) ? Owner->VerFileP : 0; + } + + // Iteration + inline VerFileIterator& operator++() {if (S != Owner->VerFileP) S = Owner->VerFileP + S->NextFile; return *this;} + inline VerFileIterator operator++(int) { VerFileIterator const tmp(*this); operator++(); return tmp; } + + // Accessors + inline PkgFileIterator File() const {return PkgFileIterator(*Owner,S->File + Owner->PkgFileP);} + + inline VerFileIterator() : Iterator<VerFile, VerFileIterator>() {} + inline VerFileIterator(pkgCache &Owner,VerFile *Trg) : Iterator<VerFile, VerFileIterator>(Owner, Trg) {} +}; + /*}}}*/ +// Description File /*{{{*/ +class pkgCache::DescFileIterator : public Iterator<DescFile, DescFileIterator> { + public: + inline DescFile* OwnerPointer() const { + return (Owner != 0) ? Owner->DescFileP : 0; + } + + // Iteration + inline DescFileIterator& operator++() {if (S != Owner->DescFileP) S = Owner->DescFileP + S->NextFile; return *this;} + inline DescFileIterator operator++(int) { DescFileIterator const tmp(*this); operator++(); return tmp; } + + // Accessors + inline PkgFileIterator File() const {return PkgFileIterator(*Owner,S->File + Owner->PkgFileP);} + + inline DescFileIterator() : Iterator<DescFile, DescFileIterator>() {} + inline DescFileIterator(pkgCache &Owner,DescFile *Trg) : Iterator<DescFile, DescFileIterator>(Owner, Trg) {} +}; + /*}}}*/ +// Inlined Begin functions can't be in the class because of order problems /*{{{*/ +inline pkgCache::PkgIterator pkgCache::GrpIterator::PackageList() const + {return PkgIterator(*Owner,Owner->PkgP + S->FirstPackage);} +inline pkgCache::VerIterator pkgCache::PkgIterator::VersionList() const + {return VerIterator(*Owner,Owner->VerP + S->VersionList);} +inline pkgCache::VerIterator pkgCache::PkgIterator::CurrentVer() const + {return VerIterator(*Owner,Owner->VerP + S->CurrentVer);} +inline pkgCache::DepIterator pkgCache::PkgIterator::RevDependsList() const + {return DepIterator(*Owner,Owner->DepP + S->RevDepends,S);} +inline pkgCache::PrvIterator pkgCache::PkgIterator::ProvidesList() const + {return PrvIterator(*Owner,Owner->ProvideP + S->ProvidesList,S);} +inline pkgCache::DescIterator pkgCache::VerIterator::DescriptionList() const + {return DescIterator(*Owner,Owner->DescP + S->DescriptionList);} +inline pkgCache::PrvIterator pkgCache::VerIterator::ProvidesList() const + {return PrvIterator(*Owner,Owner->ProvideP + S->ProvidesList,S);} +inline pkgCache::DepIterator pkgCache::VerIterator::DependsList() const + {return DepIterator(*Owner,Owner->DepP + S->DependsList,S);} +inline pkgCache::VerFileIterator pkgCache::VerIterator::FileList() const + {return VerFileIterator(*Owner,Owner->VerFileP + S->FileList);} +inline pkgCache::DescFileIterator pkgCache::DescIterator::FileList() const + {return DescFileIterator(*Owner,Owner->DescFileP + S->FileList);} +APT_DEPRECATED_MSG("Use the .Section method of VerIterator instead") inline const char * pkgCache::PkgIterator::Section() const + {return S->VersionList == 0 ? 0 : VersionList().Section();} + /*}}}*/ +#endif diff --git a/apt-pkg/cacheset.cc b/apt-pkg/cacheset.cc new file mode 100644 index 0000000..670cdba --- /dev/null +++ b/apt-pkg/cacheset.cc @@ -0,0 +1,934 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Simple wrapper around a std::set to provide a similar interface to + a set of cache structures as to the complete set of all structures + in the pkgCache. Currently only Package is supported. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cachefilter.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/policy.h> +#include <apt-pkg/versionmatch.h> + +#include <list> +#include <string> +#include <vector> +#include <regex.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include <apti18n.h> + /*}}}*/ +namespace APT { +// PackageFrom - selecting the appropriate method for package selection /*{{{*/ +bool CacheSetHelper::PackageFrom(enum PkgSelector const select, PackageContainerInterface * const pci, + pkgCacheFile &Cache, std::string const &pattern) { + switch (select) { + case UNKNOWN: return false; + case REGEX: return PackageFromRegEx(pci, Cache, pattern); + case TASK: return PackageFromTask(pci, Cache, pattern); + case FNMATCH: return PackageFromFnmatch(pci, Cache, pattern); + case PACKAGENAME: return PackageFromPackageName(pci, Cache, pattern); + case STRING: return PackageFromString(pci, Cache, pattern); + } + return false; +} + /*}}}*/ +// PackageFromTask - Return all packages in the cache from a specific task /*{{{*/ +bool CacheSetHelper::PackageFromTask(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern) { + size_t const archfound = pattern.find_last_of(':'); + std::string arch = "native"; + if (archfound != std::string::npos) { + arch = pattern.substr(archfound+1); + pattern.erase(archfound); + } + + if (pattern[pattern.length() -1] != '^') + return false; + pattern.erase(pattern.length()-1); + + if (unlikely(Cache.GetPkgCache() == 0 || Cache.GetDepCache() == 0)) + return false; + + bool const wasEmpty = pci->empty(); + if (wasEmpty == true) + pci->setConstructor(CacheSetHelper::TASK); + + // get the records + pkgRecords Recs(Cache); + + // build regexp for the task + regex_t Pattern; + char S[300]; + snprintf(S, sizeof(S), "^Task:.*[, ]%s([, ]|$)", pattern.c_str()); + if(regcomp(&Pattern,S, REG_EXTENDED | REG_NOSUB | REG_NEWLINE) != 0) { + _error->Error("Failed to compile task regexp"); + return false; + } + + bool found = false; + for (pkgCache::GrpIterator Grp = Cache->GrpBegin(); Grp.end() == false; ++Grp) { + pkgCache::PkgIterator Pkg = Grp.FindPkg(arch); + if (Pkg.end() == true) + continue; + pkgCache::VerIterator ver = Cache[Pkg].CandidateVerIter(Cache); + if(ver.end() == true) + continue; + + pkgRecords::Parser &parser = Recs.Lookup(ver.FileList()); + const char *start, *end; + parser.GetRec(start,end); + unsigned int const length = end - start; + if (unlikely(length == 0)) + continue; + char buf[length]; + strncpy(buf, start, length); + buf[length-1] = '\0'; + if (regexec(&Pattern, buf, 0, 0, 0) != 0) + continue; + + pci->insert(Pkg); + showPackageSelection(Pkg, CacheSetHelper::TASK, pattern); + found = true; + } + regfree(&Pattern); + + if (found == false) { + canNotFindPackage(CacheSetHelper::TASK, pci, Cache, pattern); + pci->setConstructor(CacheSetHelper::UNKNOWN); + return false; + } + + if (wasEmpty == false && pci->getConstructor() != CacheSetHelper::UNKNOWN) + pci->setConstructor(CacheSetHelper::UNKNOWN); + + return true; +} + /*}}}*/ +// PackageFromRegEx - Return all packages in the cache matching a pattern /*{{{*/ +bool CacheSetHelper::PackageFromRegEx(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern) { + static const char * const isregex = ".?+*|[^$"; + if (pattern.find_first_of(isregex) == std::string::npos) + return false; + + bool const wasEmpty = pci->empty(); + if (wasEmpty == true) + pci->setConstructor(CacheSetHelper::REGEX); + + size_t archfound = pattern.find_last_of(':'); + std::string arch = "native"; + if (archfound != std::string::npos) { + arch = pattern.substr(archfound+1); + if (arch.find_first_of(isregex) == std::string::npos) + pattern.erase(archfound); + else + arch = "native"; + } + + if (unlikely(Cache.GetPkgCache() == 0)) + return false; + + APT::CacheFilter::PackageNameMatchesRegEx regexfilter(pattern); + + bool found = false; + for (pkgCache::GrpIterator Grp = Cache.GetPkgCache()->GrpBegin(); Grp.end() == false; ++Grp) { + if (regexfilter(Grp) == false) + continue; + pkgCache::PkgIterator Pkg = Grp.FindPkg(arch); + if (Pkg.end() == true) { + if (archfound == std::string::npos) + Pkg = Grp.FindPreferredPkg(true); + if (Pkg.end() == true) + continue; + } + + pci->insert(Pkg); + showPackageSelection(Pkg, CacheSetHelper::REGEX, pattern); + found = true; + } + + if (found == false) { + canNotFindPackage(CacheSetHelper::REGEX, pci, Cache, pattern); + pci->setConstructor(CacheSetHelper::UNKNOWN); + return false; + } + + if (wasEmpty == false && pci->getConstructor() != CacheSetHelper::UNKNOWN) + pci->setConstructor(CacheSetHelper::UNKNOWN); + + return true; +} + /*}}}*/ +// PackageFromFnmatch - Returns the package defined by this fnmatch /*{{{*/ +bool CacheSetHelper::PackageFromFnmatch(PackageContainerInterface * const pci, + pkgCacheFile &Cache, std::string pattern) +{ + static const char * const isfnmatch = ".?*[]!"; + if (pattern.find_first_of(isfnmatch) == std::string::npos) + return false; + + bool const wasEmpty = pci->empty(); + if (wasEmpty == true) + pci->setConstructor(CacheSetHelper::FNMATCH); + + size_t archfound = pattern.find_last_of(':'); + std::string arch = "native"; + if (archfound != std::string::npos) { + arch = pattern.substr(archfound+1); + if (arch.find_first_of(isfnmatch) == std::string::npos) + pattern.erase(archfound); + else + arch = "native"; + } + + if (unlikely(Cache.GetPkgCache() == 0)) + return false; + + APT::CacheFilter::PackageNameMatchesFnmatch filter(pattern); + + bool found = false; + for (pkgCache::GrpIterator Grp = Cache.GetPkgCache()->GrpBegin(); Grp.end() == false; ++Grp) { + if (filter(Grp) == false) + continue; + pkgCache::PkgIterator Pkg = Grp.FindPkg(arch); + if (Pkg.end() == true) { + if (archfound == std::string::npos) + Pkg = Grp.FindPreferredPkg(true); + if (Pkg.end() == true) + continue; + } + + pci->insert(Pkg); + showPackageSelection(Pkg, CacheSetHelper::FNMATCH, pattern); + found = true; + } + + if (found == false) { + canNotFindPackage(CacheSetHelper::FNMATCH, pci, Cache, pattern); + pci->setConstructor(CacheSetHelper::UNKNOWN); + return false; + } + + if (wasEmpty == false && pci->getConstructor() != CacheSetHelper::UNKNOWN) + pci->setConstructor(CacheSetHelper::UNKNOWN); + + return true; +} + /*}}}*/ +// PackageFromName - Returns the package defined by this string /*{{{*/ +pkgCache::PkgIterator CacheSetHelper::PackageFromName(pkgCacheFile &Cache, + std::string const &str) { + std::string pkg = str; + size_t archfound = pkg.find_last_of(':'); + std::string arch; + if (archfound != std::string::npos) { + arch = pkg.substr(archfound+1); + pkg.erase(archfound); + } + + if (Cache.GetPkgCache() == 0) + return pkgCache::PkgIterator(Cache, 0); + + pkgCache::PkgIterator Pkg(Cache, 0); + if (arch.empty() == true) { + pkgCache::GrpIterator Grp = Cache.GetPkgCache()->FindGrp(pkg); + if (Grp.end() == false) + Pkg = Grp.FindPreferredPkg(); + } else + Pkg = Cache.GetPkgCache()->FindPkg(pkg, arch); + + if (Pkg.end() == true) + return canNotFindPkgName(Cache, str); + return Pkg; +} + /*}}}*/ +// PackageFromPackageName - Returns the package defined by this string /*{{{*/ +bool CacheSetHelper::PackageFromPackageName(PackageContainerInterface * const pci, pkgCacheFile &Cache, + std::string pkg) { + if (unlikely(Cache.GetPkgCache() == 0)) + return false; + + std::string const pkgstring = pkg; + 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"); + } + + pkgCache::GrpIterator Grp = Cache.GetPkgCache()->FindGrp(pkg); + if (Grp.end() == false) { + if (arch.empty() == true) { + pkgCache::PkgIterator Pkg = Grp.FindPreferredPkg(); + if (Pkg.end() == false) + { + pci->insert(Pkg); + return true; + } + } else { + bool found = false; + // for 'linux-any' return the first package matching, for 'linux-*' return all matches + bool const isGlobal = arch.find('*') != std::string::npos; + APT::CacheFilter::PackageArchitectureMatchesSpecification pams(arch); + for (pkgCache::PkgIterator Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg)) { + if (pams(Pkg) == false) + continue; + pci->insert(Pkg); + found = true; + if (isGlobal == false) + break; + } + if (found == true) + return true; + } + } + + pkgCache::PkgIterator Pkg = canNotFindPkgName(Cache, pkgstring); + if (Pkg.end() == true) + return false; + + pci->insert(Pkg); + return true; +} + /*}}}*/ +// PackageFromString - Return all packages matching a specific string /*{{{*/ +bool CacheSetHelper::PackageFromString(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string const &str) { + bool found = true; + _error->PushToStack(); + + if (PackageFrom(CacheSetHelper::PACKAGENAME, pci, Cache, str) == false && + PackageFrom(CacheSetHelper::TASK, pci, Cache, str) == false && + // FIXME: hm, hm, regexp/fnmatch incompatible? + PackageFrom(CacheSetHelper::FNMATCH, pci, Cache, str) == false && + PackageFrom(CacheSetHelper::REGEX, pci, Cache, str) == false) + { + canNotFindPackage(CacheSetHelper::PACKAGENAME, pci, Cache, str); + found = false; + } + + if (found == true) + _error->RevertToStack(); + else + _error->MergeWithStack(); + return found; +} + /*}}}*/ +// PackageFromCommandLine - Return all packages specified on commandline /*{{{*/ +bool CacheSetHelper::PackageFromCommandLine(PackageContainerInterface * const pci, pkgCacheFile &Cache, const char **cmdline) { + bool found = false; + for (const char **I = cmdline; *I != 0; ++I) + found |= PackageFrom(CacheSetHelper::STRING, pci, Cache, *I); + return found; +} + /*}}}*/ +// FromModifierCommandLine - helper doing the work for PKG:GroupedFromCommandLine /*{{{*/ +bool CacheSetHelper::PackageFromModifierCommandLine(unsigned short &modID, PackageContainerInterface * const pci, + pkgCacheFile &Cache, const char * cmdline, + std::list<PkgModifier> const &mods) { + std::string str = cmdline; + unsigned short fallback = modID; + bool modifierPresent = false; + for (std::list<PkgModifier>::const_iterator mod = mods.begin(); + mod != mods.end(); ++mod) { + size_t const alength = strlen(mod->Alias); + switch(mod->Pos) { + case PkgModifier::POSTFIX: + if (str.compare(str.length() - alength, alength, + mod->Alias, 0, alength) != 0) + continue; + str.erase(str.length() - alength); + modID = mod->ID; + break; + case PkgModifier::PREFIX: + continue; + case PkgModifier::NONE: + continue; + } + modifierPresent = true; + break; + } + if (modifierPresent == true) { + bool const errors = showErrors(false); + bool const found = PackageFrom(PACKAGENAME, pci, Cache, cmdline); + showErrors(errors); + if (found == true) { + modID = fallback; + return true; + } + } + return PackageFrom(CacheSetHelper::PACKAGENAME, pci, Cache, str); +} + /*}}}*/ +// FromModifierCommandLine - helper doing the work for VER:GroupedFromCommandLine /*{{{*/ +bool VersionContainerInterface::FromModifierCommandLine(unsigned short &modID, + VersionContainerInterface * const vci, + pkgCacheFile &Cache, const char * cmdline, + std::list<Modifier> const &mods, + CacheSetHelper &helper) { + CacheSetHelper::VerSelector select = CacheSetHelper::NEWEST; + std::string str = cmdline; + if (unlikely(str.empty() == true)) + return false; + bool modifierPresent = false; + unsigned short fallback = modID; + for (std::list<Modifier>::const_iterator mod = mods.begin(); + mod != mods.end(); ++mod) { + if (modID == fallback && mod->ID == fallback) + select = mod->SelectVersion; + size_t const alength = strlen(mod->Alias); + switch(mod->Pos) { + case Modifier::POSTFIX: + if (str.length() <= alength || + str.compare(str.length() - alength, alength, mod->Alias, 0, alength) != 0) + continue; + str.erase(str.length() - alength); + modID = mod->ID; + select = mod->SelectVersion; + break; + case Modifier::PREFIX: + continue; + case Modifier::NONE: + continue; + } + modifierPresent = true; + break; + } + if (modifierPresent == true) { + bool const errors = helper.showErrors(false); + bool const found = VersionContainerInterface::FromString(vci, Cache, cmdline, select, helper, true); + helper.showErrors(errors); + if (found == true) { + modID = fallback; + return true; + } + } + return FromString(vci, Cache, str, select, helper); +} + /*}}}*/ +// FromCommandLine - Return all versions specified on commandline /*{{{*/ +bool VersionContainerInterface::FromCommandLine(VersionContainerInterface * const vci, + pkgCacheFile &Cache, const char **cmdline, + CacheSetHelper::VerSelector const fallback, + CacheSetHelper &helper) { + bool found = false; + for (const char **I = cmdline; *I != 0; ++I) + found |= VersionContainerInterface::FromString(vci, Cache, *I, fallback, helper); + return found; +} + /*}}}*/ +// FromString - Returns all versions spedcified by a string /*{{{*/ +bool VersionContainerInterface::FromString(VersionContainerInterface * const vci, + pkgCacheFile &Cache, std::string pkg, + CacheSetHelper::VerSelector const fallback, + CacheSetHelper &helper, + bool const onlyFromName) { + std::string ver; + bool verIsRel = false; + size_t const vertag = pkg.find_last_of("/="); + if (vertag != std::string::npos) { + ver = pkg.substr(vertag+1); + verIsRel = (pkg[vertag] == '/'); + pkg.erase(vertag); + } + + PackageSet pkgset; + if (onlyFromName == false) + helper.PackageFrom(CacheSetHelper::STRING, &pkgset, Cache, pkg); + else { + helper.PackageFrom(CacheSetHelper::PACKAGENAME, &pkgset, Cache, pkg); + } + + bool errors = true; + if (pkgset.getConstructor() != CacheSetHelper::UNKNOWN) + errors = helper.showErrors(false); + + bool found = false; + for (PackageSet::const_iterator P = pkgset.begin(); + P != pkgset.end(); ++P) { + if (vertag == std::string::npos) { + found |= VersionContainerInterface::FromPackage(vci, Cache, P, fallback, helper); + continue; + } + pkgCache::VerIterator V; + if (ver == "installed") + V = getInstalledVer(Cache, P, helper); + else if (ver == "candidate") + V = getCandidateVer(Cache, P, helper); + else if (ver == "newest") { + if (P->VersionList != 0) + V = P.VersionList(); + else + V = helper.canNotGetVersion(CacheSetHelper::NEWEST, Cache, P); + } else { + pkgVersionMatch Match(ver, (verIsRel == true ? pkgVersionMatch::Release : + pkgVersionMatch::Version)); + V = Match.Find(P); + if (V.end() == true) { + if (verIsRel == true) + _error->Error(_("Release '%s' for '%s' was not found"), + ver.c_str(), P.FullName(true).c_str()); + else + _error->Error(_("Version '%s' for '%s' was not found"), + ver.c_str(), P.FullName(true).c_str()); + continue; + } + } + if (V.end() == true) + continue; + if (verIsRel == true) + helper.showVersionSelection(P, V, CacheSetHelper::RELEASE, ver); + else + helper.showVersionSelection(P, V, CacheSetHelper::VERSIONNUMBER, ver); + vci->insert(V); + found = true; + } + if (pkgset.getConstructor() != CacheSetHelper::UNKNOWN) + helper.showErrors(errors); + return found; +} + /*}}}*/ +// FromPackage - versions from package based on fallback /*{{{*/ +bool VersionContainerInterface::FromPackage(VersionContainerInterface * const vci, + pkgCacheFile &Cache, + pkgCache::PkgIterator const &P, + CacheSetHelper::VerSelector const fallback, + CacheSetHelper &helper) { + pkgCache::VerIterator V; + bool showErrors; + bool found = false; + switch(fallback) { + case CacheSetHelper::ALL: + if (P->VersionList != 0) + for (V = P.VersionList(); V.end() != true; ++V) + found |= vci->insert(V); + else + helper.canNotFindVersion(CacheSetHelper::ALL, vci, Cache, P); + break; + case CacheSetHelper::CANDANDINST: + found |= vci->insert(getInstalledVer(Cache, P, helper)); + found |= vci->insert(getCandidateVer(Cache, P, helper)); + break; + case CacheSetHelper::CANDIDATE: + found |= vci->insert(getCandidateVer(Cache, P, helper)); + break; + case CacheSetHelper::INSTALLED: + found |= vci->insert(getInstalledVer(Cache, P, helper)); + break; + case CacheSetHelper::CANDINST: + showErrors = helper.showErrors(false); + V = getCandidateVer(Cache, P, helper); + if (V.end() == true) + V = getInstalledVer(Cache, P, helper); + helper.showErrors(showErrors); + if (V.end() == false) + found |= vci->insert(V); + else + helper.canNotFindVersion(CacheSetHelper::CANDINST, vci, Cache, P); + break; + case CacheSetHelper::INSTCAND: + showErrors = helper.showErrors(false); + V = getInstalledVer(Cache, P, helper); + if (V.end() == true) + V = getCandidateVer(Cache, P, helper); + helper.showErrors(showErrors); + if (V.end() == false) + found |= vci->insert(V); + else + helper.canNotFindVersion(CacheSetHelper::INSTCAND, vci, Cache, P); + break; + case CacheSetHelper::NEWEST: + if (P->VersionList != 0) + found |= vci->insert(P.VersionList()); + else + helper.canNotFindVersion(CacheSetHelper::NEWEST, vci, Cache, P); + break; + case CacheSetHelper::RELEASE: + case CacheSetHelper::VERSIONNUMBER: + // both make no sense here, so always false + return false; + } + return found; +} + /*}}}*/ +// FromDependency - versions satisfying a given dependency /*{{{*/ +bool VersionContainerInterface::FromDependency(VersionContainerInterface * const vci, + pkgCacheFile &Cache, + pkgCache::DepIterator const &D, + CacheSetHelper::VerSelector const selector, + CacheSetHelper &helper) +{ + bool found = false; + switch(selector) { + case CacheSetHelper::ALL: + { + pkgCache::PkgIterator const T = D.TargetPkg(); + for (pkgCache::VerIterator Ver = T.VersionList(); Ver.end() == false; ++Ver) + { + if (D.IsSatisfied(Ver) == true) + { + vci->insert(Ver); + found = true; + } + for (pkgCache::PrvIterator Prv = T.ProvidesList(); Prv.end() != true; ++Prv) + { + pkgCache::VerIterator const V = Prv.OwnerVer(); + if (unlikely(V.end() == true) || D.IsSatisfied(Prv) == false) + continue; + vci->insert(V); + found = true; + } + } + return found; + } + case CacheSetHelper::CANDANDINST: + { + found = FromDependency(vci, Cache, D, CacheSetHelper::CANDIDATE, helper); + found &= FromDependency(vci, Cache, D, CacheSetHelper::INSTALLED, helper); + return found; + } + case CacheSetHelper::CANDIDATE: + { + pkgCache::PkgIterator const T = D.TargetPkg(); + pkgCache::VerIterator const Cand = Cache[T].CandidateVerIter(Cache); + if (Cand.end() == false && D.IsSatisfied(Cand) == true) + { + vci->insert(Cand); + found = true; + } + for (pkgCache::PrvIterator Prv = T.ProvidesList(); Prv.end() != true; ++Prv) + { + pkgCache::VerIterator const V = Prv.OwnerVer(); + pkgCache::VerIterator const Cand = Cache[Prv.OwnerPkg()].CandidateVerIter(Cache); + if (Cand.end() == true || V != Cand || D.IsSatisfied(Prv) == false) + continue; + vci->insert(Cand); + found = true; + } + return found; + } + case CacheSetHelper::INSTALLED: + { + pkgCache::PkgIterator const T = D.TargetPkg(); + pkgCache::VerIterator const Cand = T.CurrentVer(); + if (Cand.end() == false && D.IsSatisfied(Cand) == true) + { + vci->insert(Cand); + found = true; + } + for (pkgCache::PrvIterator Prv = T.ProvidesList(); Prv.end() != true; ++Prv) + { + pkgCache::VerIterator const V = Prv.OwnerVer(); + pkgCache::VerIterator const Cand = Prv.OwnerPkg().CurrentVer(); + if (Cand.end() == true || V != Cand || D.IsSatisfied(Prv) == false) + continue; + vci->insert(Cand); + found = true; + } + return found; + } + case CacheSetHelper::CANDINST: + return FromDependency(vci, Cache, D, CacheSetHelper::CANDIDATE, helper) || + FromDependency(vci, Cache, D, CacheSetHelper::INSTALLED, helper); + case CacheSetHelper::INSTCAND: + return FromDependency(vci, Cache, D, CacheSetHelper::INSTALLED, helper) || + FromDependency(vci, Cache, D, CacheSetHelper::CANDIDATE, helper); + case CacheSetHelper::NEWEST: + { + pkgCache::PkgIterator const T = D.TargetPkg(); + pkgCache::VerIterator const Cand = T.VersionList(); + if (Cand.end() == false && D.IsSatisfied(Cand) == true) + { + vci->insert(Cand); + found = true; + } + for (pkgCache::PrvIterator Prv = T.ProvidesList(); Prv.end() != true; ++Prv) + { + pkgCache::VerIterator const V = Prv.OwnerVer(); + pkgCache::VerIterator const Cand = Prv.OwnerPkg().VersionList(); + if (Cand.end() == true || V != Cand || D.IsSatisfied(Prv) == false) + continue; + vci->insert(Cand); + found = true; + } + return found; + } + case CacheSetHelper::RELEASE: + case CacheSetHelper::VERSIONNUMBER: + // both make no sense here, so always false + return false; + } + return found; +} + /*}}}*/ +// getCandidateVer - Returns the candidate version of the given package /*{{{*/ +pkgCache::VerIterator VersionContainerInterface::getCandidateVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg, CacheSetHelper &helper) { + pkgCache::VerIterator Cand; + if (Cache.IsDepCacheBuilt() == true) { + Cand = Cache[Pkg].CandidateVerIter(Cache); + } else if (unlikely(Cache.GetPolicy() == nullptr)) { + return pkgCache::VerIterator(Cache); + } else { + Cand = Cache.GetPolicy()->GetCandidateVer(Pkg); + } + if (Cand.end() == true) + return helper.canNotGetVersion(CacheSetHelper::CANDIDATE, Cache, Pkg); + return Cand; +} + /*}}}*/ +// getInstalledVer - Returns the installed version of the given package /*{{{*/ +pkgCache::VerIterator VersionContainerInterface::getInstalledVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg, CacheSetHelper &helper) { + if (Pkg->CurrentVer == 0) + return helper.canNotGetVersion(CacheSetHelper::INSTALLED, Cache, Pkg); + return Pkg.CurrentVer(); +} + /*}}}*/ + +// canNotFindPackage - with the given selector and pattern /*{{{*/ +void CacheSetHelper::canNotFindPackage(enum PkgSelector const select, + PackageContainerInterface * const pci, pkgCacheFile &Cache, + std::string const &pattern) { + switch (select) { +APT_IGNORE_DEPRECATED_PUSH + case REGEX: canNotFindRegEx(pci, Cache, pattern); break; + case TASK: canNotFindTask(pci, Cache, pattern); break; + case FNMATCH: canNotFindFnmatch(pci, Cache, pattern); break; + case PACKAGENAME: canNotFindPackage(pci, Cache, pattern); break; + case STRING: canNotFindPackage(pci, Cache, pattern); break; + case UNKNOWN: break; +APT_IGNORE_DEPRECATED_POP + } +} +// canNotFindTask - handle the case no package is found for a task /*{{{*/ +void CacheSetHelper::canNotFindTask(PackageContainerInterface * const /*pci*/, pkgCacheFile &/*Cache*/, std::string pattern) { + if (ShowError == true) + _error->Insert(ErrorType, _("Couldn't find task '%s'"), pattern.c_str()); +} + /*}}}*/ +// canNotFindRegEx - handle the case no package is found by a regex /*{{{*/ +void CacheSetHelper::canNotFindRegEx(PackageContainerInterface * const /*pci*/, pkgCacheFile &/*Cache*/, std::string pattern) { + if (ShowError == true) + _error->Insert(ErrorType, _("Couldn't find any package by regex '%s'"), pattern.c_str()); +} + /*}}}*/ +// canNotFindFnmatch - handle the case no package is found by a fnmatch /*{{{*/ + void CacheSetHelper::canNotFindFnmatch(PackageContainerInterface * const /*pci*/, pkgCacheFile &/*Cache*/, std::string pattern) { + if (ShowError == true) + _error->Insert(ErrorType, _("Couldn't find any package by glob '%s'"), pattern.c_str()); +} + /*}}}*/ +// canNotFindPackage - handle the case no package is found from a string/*{{{*/ +void CacheSetHelper::canNotFindPackage(PackageContainerInterface * const /*pci*/, pkgCacheFile &/*Cache*/, std::string const &/*str*/) { +} + /*}}}*/ + /*}}}*/ +// canNotFindPkgName - handle the case no package has this name /*{{{*/ +pkgCache::PkgIterator CacheSetHelper::canNotFindPkgName(pkgCacheFile &Cache, + std::string const &str) { + if (ShowError == true) + _error->Insert(ErrorType, _("Unable to locate package %s"), str.c_str()); + return pkgCache::PkgIterator(Cache, 0); +} + /*}}}*/ +// canNotFindVersion - for package by selector /*{{{*/ +void CacheSetHelper::canNotFindVersion(enum VerSelector const select, VersionContainerInterface * const vci, pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg) +{ + switch (select) { +APT_IGNORE_DEPRECATED_PUSH + case ALL: canNotFindAllVer(vci, Cache, Pkg); break; + case INSTCAND: canNotFindInstCandVer(vci, Cache, Pkg); break; + case CANDINST: canNotFindCandInstVer(vci, Cache, Pkg); break; + case NEWEST: canNotFindNewestVer(Cache, Pkg); break; + case CANDIDATE: canNotFindCandidateVer(Cache, Pkg); break; + case INSTALLED: canNotFindInstalledVer(Cache, Pkg); break; +APT_IGNORE_DEPRECATED_POP + case CANDANDINST: canNotGetCandInstVer(Cache, Pkg); break; + case RELEASE: + case VERSIONNUMBER: + // invalid in this branch + break; + } +} +// canNotFindAllVer /*{{{*/ +void CacheSetHelper::canNotFindAllVer(VersionContainerInterface * const /*vci*/, pkgCacheFile &/*Cache*/, + pkgCache::PkgIterator const &Pkg) { + if (ShowError == true) + _error->Insert(ErrorType, _("Can't select versions from package '%s' as it is purely virtual"), Pkg.FullName(true).c_str()); +} + /*}}}*/ +// canNotFindInstCandVer /*{{{*/ +void CacheSetHelper::canNotFindInstCandVer(VersionContainerInterface * const /*vci*/, pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg) { + canNotGetInstCandVer(Cache, Pkg); +} + /*}}}*/ +// canNotFindInstCandVer /*{{{*/ +void CacheSetHelper::canNotFindCandInstVer(VersionContainerInterface * const /*vci*/, pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg) { + canNotGetCandInstVer(Cache, Pkg); +} + /*}}}*/ + /*}}}*/ +// canNotGetVersion - for package by selector /*{{{*/ +pkgCache::VerIterator CacheSetHelper::canNotGetVersion(enum VerSelector const select, pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg) { + switch (select) { +APT_IGNORE_DEPRECATED_PUSH + case NEWEST: return canNotFindNewestVer(Cache, Pkg); + case CANDIDATE: return canNotFindCandidateVer(Cache, Pkg); + case INSTALLED: return canNotFindInstalledVer(Cache, Pkg); +APT_IGNORE_DEPRECATED_POP + case CANDINST: return canNotGetCandInstVer(Cache, Pkg); + case INSTCAND: return canNotGetInstCandVer(Cache, Pkg); + case ALL: + case CANDANDINST: + case RELEASE: + case VERSIONNUMBER: + // invalid in this branch + return pkgCache::VerIterator(Cache, 0); + } + return pkgCache::VerIterator(Cache, 0); +} +// canNotFindNewestVer /*{{{*/ +pkgCache::VerIterator CacheSetHelper::canNotFindNewestVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg) { + if (ShowError == true) + _error->Insert(ErrorType, _("Can't select newest version from package '%s' as it is purely virtual"), Pkg.FullName(true).c_str()); + return pkgCache::VerIterator(Cache, 0); +} + /*}}}*/ +// canNotFindCandidateVer /*{{{*/ +pkgCache::VerIterator CacheSetHelper::canNotFindCandidateVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg) { + if (ShowError == true) + _error->Insert(ErrorType, _("Can't select candidate version from package %s as it has no candidate"), Pkg.FullName(true).c_str()); + return pkgCache::VerIterator(Cache, 0); +} + /*}}}*/ +// canNotFindInstalledVer /*{{{*/ +pkgCache::VerIterator CacheSetHelper::canNotFindInstalledVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg) { + if (ShowError == true) + _error->Insert(ErrorType, _("Can't select installed version from package %s as it is not installed"), Pkg.FullName(true).c_str()); + return pkgCache::VerIterator(Cache, 0); +} + /*}}}*/ +// canNotFindInstCandVer /*{{{*/ +pkgCache::VerIterator CacheSetHelper::canNotGetInstCandVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg) { + if (ShowError == true) + _error->Insert(ErrorType, _("Can't select installed nor candidate version from package '%s' as it has neither of them"), Pkg.FullName(true).c_str()); + return pkgCache::VerIterator(Cache, 0); +} + /*}}}*/ +// canNotFindInstCandVer /*{{{*/ +pkgCache::VerIterator CacheSetHelper::canNotGetCandInstVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg) { + if (ShowError == true) + _error->Insert(ErrorType, _("Can't select installed nor candidate version from package '%s' as it has neither of them"), Pkg.FullName(true).c_str()); + return pkgCache::VerIterator(Cache, 0); +} + /*}}}*/ + /*}}}*/ +// showPackageSelection - by selector and given pattern /*{{{*/ +void CacheSetHelper::showPackageSelection(pkgCache::PkgIterator const &pkg, enum PkgSelector const select, + std::string const &pattern) { + switch (select) { +APT_IGNORE_DEPRECATED_PUSH + case REGEX: showRegExSelection(pkg, pattern); break; + case TASK: showTaskSelection(pkg, pattern); break; + case FNMATCH: showFnmatchSelection(pkg, pattern); break; +APT_IGNORE_DEPRECATED_POP + case PACKAGENAME: /* no surprises here */ break; + case STRING: /* handled by the special cases */ break; + case UNKNOWN: break; + } +} +// showTaskSelection /*{{{*/ +void CacheSetHelper::showTaskSelection(pkgCache::PkgIterator const &/*pkg*/, + std::string const &/*pattern*/) { +} + /*}}}*/ +// showRegExSelection /*{{{*/ +void CacheSetHelper::showRegExSelection(pkgCache::PkgIterator const &/*pkg*/, + std::string const &/*pattern*/) { +} + /*}}}*/ +// showFnmatchSelection /*{{{*/ +void CacheSetHelper::showFnmatchSelection(pkgCache::PkgIterator const &/*pkg*/, + std::string const &/*pattern*/) { +} + /*}}}*/ + /*}}}*/ +// showVersionSelection /*{{{*/ +void CacheSetHelper::showVersionSelection(pkgCache::PkgIterator const &Pkg, + pkgCache::VerIterator const &Ver, enum VerSelector const select, std::string const &pattern) { + switch (select) { +APT_IGNORE_DEPRECATED_PUSH + case RELEASE: + showSelectedVersion(Pkg, Ver, pattern, true); + break; + case VERSIONNUMBER: + showSelectedVersion(Pkg, Ver, pattern, false); + break; +APT_IGNORE_DEPRECATED_POP + case NEWEST: + case CANDIDATE: + case INSTALLED: + case CANDINST: + case INSTCAND: + case ALL: + case CANDANDINST: + // not really surprises, but in fact: just not implemented + break; + } +} +void CacheSetHelper::showSelectedVersion(pkgCache::PkgIterator const &/*Pkg*/, + pkgCache::VerIterator const /*Ver*/, + std::string const &/*ver*/, + bool const /*verIsRel*/) { +} + /*}}}*/ + +CacheSetHelper::CacheSetHelper(bool const ShowError, GlobalError::MsgType ErrorType) : + ShowError(ShowError), ErrorType(ErrorType), d(NULL) {} +CacheSetHelper::~CacheSetHelper() {} + +PackageContainerInterface::PackageContainerInterface() : ConstructedBy(CacheSetHelper::UNKNOWN), d(NULL) {} +PackageContainerInterface::PackageContainerInterface(CacheSetHelper::PkgSelector const by) : ConstructedBy(by), d(NULL) {} +PackageContainerInterface& PackageContainerInterface::operator=(PackageContainerInterface const &other) { + if (this != &other) + this->ConstructedBy = other.ConstructedBy; + return *this; +} +PackageContainerInterface::~PackageContainerInterface() {} + +PackageUniverse::PackageUniverse(pkgCache * const Owner) : _cont(Owner), d(NULL) {} +PackageUniverse::PackageUniverse(pkgCacheFile * const Owner) : _cont(Owner->GetPkgCache()), d(NULL) {} +PackageUniverse::~PackageUniverse() {} + +VersionContainerInterface::VersionContainerInterface() : d(NULL) {} +VersionContainerInterface& VersionContainerInterface::operator=(VersionContainerInterface const &) { + return *this; +} + +VersionContainerInterface::~VersionContainerInterface() {} +} diff --git a/apt-pkg/cacheset.h b/apt-pkg/cacheset.h new file mode 100644 index 0000000..b00fc73 --- /dev/null +++ b/apt-pkg/cacheset.h @@ -0,0 +1,1229 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/** \file cacheset.h + Wrappers around std::set to have set::iterators which behave + similar to the Iterators of the cache structures. + + Provides also a few helper methods which work with these sets */ + /*}}}*/ +#ifndef APT_CACHESET_H +#define APT_CACHESET_H +// Include Files /*{{{*/ +#include <fstream> +#include <map> +#include <set> +#if __cplusplus >= 201103L +#include <forward_list> +#include <initializer_list> +#include <unordered_set> +#endif +#include <algorithm> +#include <deque> +#include <iterator> +#include <list> +#include <string> +#include <vector> + +#include <stddef.h> + +#include <apt-pkg/error.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/cachefile.h> +#endif +#ifndef APT_10_CLEANER_HEADERS +#include <iostream> +#endif + /*}}}*/ + +class pkgCacheFile; + +namespace APT { +class PackageContainerInterface; +class VersionContainerInterface; + +class CacheSetHelper { /*{{{*/ +/** \class APT::CacheSetHelper + Simple base class with a lot of virtual methods which can be overridden + to alter the behavior or the output of the CacheSets. + + This helper is passed around by the static methods in the CacheSets and + used every time they hit an error condition or something could be + printed out. +*/ +public: /*{{{*/ + CacheSetHelper(bool const ShowError = true, + GlobalError::MsgType ErrorType = GlobalError::ERROR); + virtual ~CacheSetHelper(); + + enum PkgSelector { UNKNOWN, REGEX, TASK, FNMATCH, PACKAGENAME, STRING }; + + virtual bool PackageFrom(enum PkgSelector const select, PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string const &pattern); + + virtual bool PackageFromCommandLine(PackageContainerInterface * const pci, pkgCacheFile &Cache, const char **cmdline); + + struct PkgModifier { + enum Position { NONE, PREFIX, POSTFIX }; + unsigned short ID; + const char * const Alias; + Position Pos; + PkgModifier (unsigned short const &id, const char * const alias, Position const &pos) : ID(id), Alias(alias), Pos(pos) {} + }; + virtual bool PackageFromModifierCommandLine(unsigned short &modID, PackageContainerInterface * const pci, + pkgCacheFile &Cache, const char * cmdline, + std::list<PkgModifier> const &mods); + + APT_DEPRECATED_MSG("use .PackageFrom(PACKAGENAME, …) instead") pkgCache::PkgIterator PackageFromName(pkgCacheFile &Cache, std::string const &pattern); + + /** \brief be notified about the package being selected via pattern + * + * Main use is probably to show a message to the user what happened + * + * \param pkg is the package which was selected + * \param select is the selection method which choose the package + * \param pattern is the string used by the selection method to pick the package + */ + virtual void showPackageSelection(pkgCache::PkgIterator const &pkg, PkgSelector const select, std::string const &pattern); + // use the method above instead, react only on the type you need and let the base handle the rest if need be + // this allows us to add new selection methods without breaking the ABI constantly with new virtual methods + APT_DEPRECATED_MSG("override .showPackageSelection and select with switch") virtual void showTaskSelection(pkgCache::PkgIterator const &pkg, std::string const &pattern); + APT_DEPRECATED_MSG("override .showPackageSelection and select with switch") virtual void showRegExSelection(pkgCache::PkgIterator const &pkg, std::string const &pattern); + APT_DEPRECATED_MSG("override .showPackageSelection and select with switch") virtual void showFnmatchSelection(pkgCache::PkgIterator const &pkg, std::string const &pattern); + + /** \brief be notified if a package can't be found via pattern + * + * Can be used to show a message as well as to try something else to make it match + * + * \param select is the method tried for selection + * \param pci is the container the package should be inserted in + * \param Cache is the package universe available + * \param pattern is the string not matching anything + */ + virtual void canNotFindPackage(enum PkgSelector const select, PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string const &pattern); + // same as above for showPackageSelection + APT_DEPRECATED_MSG("override .canNotFindPackage and select with switch") virtual void canNotFindTask(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern); + APT_DEPRECATED_MSG("override .canNotFindPackage and select with switch") virtual void canNotFindRegEx(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern); + APT_DEPRECATED_MSG("override .canNotFindPackage and select with switch") virtual void canNotFindFnmatch(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern); + APT_DEPRECATED_MSG("override .canNotFindPackage and select with switch") virtual void canNotFindPackage(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string const &str); + + /** \brief specifies which version(s) we want to refer to */ + enum VerSelector { + /** by release string */ + RELEASE, + /** by version number string */ + VERSIONNUMBER, + /** All versions */ + ALL, + /** Candidate and installed version */ + CANDANDINST, + /** Candidate version */ + CANDIDATE, + /** Installed version */ + INSTALLED, + /** Candidate or if non installed version */ + CANDINST, + /** Installed or if non candidate version */ + INSTCAND, + /** Newest version */ + NEWEST + }; + + /** \brief be notified about the version being selected via pattern + * + * Main use is probably to show a message to the user what happened + * Note that at the moment this method is only called for RELEASE + * and VERSION selections, not for the others. + * + * \param Pkg is the package which was selected for + * \param Ver is the version selected + * \param select is the selection method which choose the version + * \param pattern is the string used by the selection method to pick the version + */ + virtual void showVersionSelection(pkgCache::PkgIterator const &Pkg, pkgCache::VerIterator const &Ver, + enum VerSelector const select, std::string const &pattern); + APT_DEPRECATED_MSG("use .showVersionSelection instead, similar to .showPackageSelection") virtual void showSelectedVersion(pkgCache::PkgIterator const &Pkg, pkgCache::VerIterator const Ver, + std::string const &ver, bool const verIsRel); + + /** \brief be notified if a version can't be found for a package + * + * Main use is probably to show a message to the user what happened + * + * \param select is the method tried for selection + * \param vci is the container the version should be inserted in + * \param Cache is the package universe available + * \param Pkg is the package we wanted a version from + */ + virtual void canNotFindVersion(enum VerSelector const select, VersionContainerInterface * const vci, pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg); + APT_DEPRECATED_MSG("override .canNotFindVersion and select via switch") virtual void canNotFindAllVer(VersionContainerInterface * const vci, pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg); + APT_DEPRECATED_MSG("override .canNotFindVersion and select via switch") virtual void canNotFindInstCandVer(VersionContainerInterface * const vci, pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg); + APT_DEPRECATED_MSG("override .canNotFindVersion and select via switch") virtual void canNotFindCandInstVer(VersionContainerInterface * const vci, + pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg); + + // the difference between canNotFind and canNotGet is that the later is more low-level + // and called from other places: In this case looking into the code is the only real answer… + virtual pkgCache::VerIterator canNotGetVersion(enum VerSelector const select, pkgCacheFile &Cache, pkgCache::PkgIterator const &Pkg); + APT_DEPRECATED_MSG("override .canNotGetVersion and select via switch") virtual pkgCache::VerIterator canNotFindNewestVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg); + APT_DEPRECATED_MSG("override .canNotGetVersion and select via switch") virtual pkgCache::VerIterator canNotFindCandidateVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg); + APT_DEPRECATED_MSG("override .canNotGetVersion and select via switch") virtual pkgCache::VerIterator canNotFindInstalledVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg); + + virtual pkgCache::PkgIterator canNotFindPkgName(pkgCacheFile &Cache, std::string const &str); + + bool showErrors() const { return ShowError; } + bool showErrors(bool const newValue) { if (ShowError == newValue) return ShowError; else return ((ShowError = newValue) == false); } + GlobalError::MsgType errorType() const { return ErrorType; } + GlobalError::MsgType errorType(GlobalError::MsgType const &newValue) + { + if (ErrorType == newValue) return ErrorType; + else { + GlobalError::MsgType const &oldValue = ErrorType; + ErrorType = newValue; + return oldValue; + } + } + + /*}}}*/ +protected: + bool ShowError; + GlobalError::MsgType ErrorType; + + pkgCache::VerIterator canNotGetInstCandVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg); + pkgCache::VerIterator canNotGetCandInstVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg); + + bool PackageFromTask(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern); + bool PackageFromRegEx(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern); + bool PackageFromFnmatch(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern); + bool PackageFromPackageName(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern); + bool PackageFromString(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string const &pattern); +private: + void * const d; +}; /*}}}*/ +// Iterator templates for our Containers /*{{{*/ +template<typename Interface, typename Master, typename iterator_type, typename container_iterator, typename container_value> class Container_iterator_base : + public std::iterator<typename std::iterator_traits<container_iterator>::iterator_category, container_value>, + public Interface::template iterator_base<iterator_type> +{ +protected: + container_iterator _iter; +public: + explicit Container_iterator_base(container_iterator i) : _iter(i) {} + inline container_value operator*(void) const { return static_cast<iterator_type const*>(this)->getType(); }; + operator container_iterator(void) const { return _iter; } + inline iterator_type& operator++() { ++_iter; return static_cast<iterator_type&>(*this); } + inline iterator_type operator++(int) { iterator_type tmp(*this); operator++(); return tmp; } + inline iterator_type operator+(typename container_iterator::difference_type const &n) { return iterator_type(_iter + n); } + inline iterator_type operator+=(typename container_iterator::difference_type const &n) { _iter += n; return static_cast<iterator_type&>(*this); } + inline iterator_type& operator--() { --_iter;; return static_cast<iterator_type&>(*this); } + inline iterator_type operator--(int) { iterator_type tmp(*this); operator--(); return tmp; } + inline iterator_type operator-(typename container_iterator::difference_type const &n) { return iterator_type(_iter - n); } + inline typename container_iterator::difference_type operator-(iterator_type const &b) { return (_iter - b._iter); } + inline iterator_type operator-=(typename container_iterator::difference_type const &n) { _iter -= n; return static_cast<iterator_type&>(*this); } + inline bool operator!=(iterator_type const &i) const { return _iter != i._iter; } + inline bool operator==(iterator_type const &i) const { return _iter == i._iter; } + inline bool operator<(iterator_type const &i) const { return _iter < i._iter; } + inline bool operator>(iterator_type const &i) const { return _iter > i._iter; } + inline bool operator<=(iterator_type const &i) const { return _iter <= i._iter; } + inline bool operator>=(iterator_type const &i) const { return _iter >= i._iter; } + inline typename container_iterator::reference operator[](typename container_iterator::difference_type const &n) const { return _iter[n]; } + + friend std::ostream& operator<<(std::ostream& out, iterator_type i) { return operator<<(out, *i); } + friend Master; +}; +template<class Interface, class Container, class Master> class Container_const_iterator : + public Container_iterator_base<Interface, Master, Container_const_iterator<Interface, Container, Master>, typename Container::const_iterator, typename Container::value_type> +{ + typedef Container_const_iterator<Interface, Container, Master> iterator_type; + typedef typename Container::const_iterator container_iterator; +public: + explicit Container_const_iterator(container_iterator i) : + Container_iterator_base<Interface, Master, iterator_type, container_iterator, typename Container::value_type>(i) {} + + inline typename Container::value_type getType(void) const { return *this->_iter; } +}; +template<class Interface, class Container, class Master> class Container_iterator : + public Container_iterator_base<Interface, Master, Container_iterator<Interface, Container, Master>, typename Container::iterator, typename Container::value_type> +{ + typedef Container_iterator<Interface, Container, Master> iterator_type; + typedef typename Container::iterator container_iterator; +public: + explicit Container_iterator(container_iterator i) : + Container_iterator_base<Interface, Master, iterator_type, container_iterator, typename Container::value_type>(i) {} + + operator typename Master::const_iterator() { return typename Master::const_iterator(this->_iter); } + inline iterator_type& operator=(iterator_type const &i) { this->_iter = i._iter; return static_cast<iterator_type&>(*this); } + inline iterator_type& operator=(container_iterator const &i) { this->_iter = i; return static_cast<iterator_type&>(*this); } + inline typename Container::iterator::reference operator*(void) const { return *this->_iter; } + + inline typename Container::value_type getType(void) const { return *this->_iter; } +}; +template<class Interface, class Container, class Master> class Container_const_reverse_iterator : + public Container_iterator_base<Interface, Master, Container_const_reverse_iterator<Interface, Container, Master>, typename Container::const_reverse_iterator, typename Container::value_type> +{ + typedef Container_const_reverse_iterator<Interface, Container, Master> iterator_type; + typedef typename Container::const_reverse_iterator container_iterator; +public: + explicit Container_const_reverse_iterator(container_iterator i) : + Container_iterator_base<Interface, Master, iterator_type, container_iterator, typename Container::value_type>(i) {} + + inline typename Container::value_type getType(void) const { return *this->_iter; } +}; +template<class Interface, class Container, class Master> class Container_reverse_iterator : + public Container_iterator_base<Interface, Master, Container_reverse_iterator<Interface, Container, Master>, typename Container::reverse_iterator, typename Container::value_type> +{ + typedef Container_reverse_iterator<Interface, Container, Master> iterator_type; + typedef typename Container::reverse_iterator container_iterator; +public: + explicit Container_reverse_iterator(container_iterator i) : + Container_iterator_base<Interface, Master, iterator_type, container_iterator, typename Container::value_type>(i) {} + + operator typename Master::const_iterator() { return typename Master::const_iterator(this->_iter); } + inline iterator_type& operator=(iterator_type const &i) { this->_iter = i._iter; return static_cast<iterator_type&>(*this); } + inline iterator_type& operator=(container_iterator const &i) { this->_iter = i; return static_cast<iterator_type&>(*this); } + inline typename Container::reverse_iterator::reference operator*(void) const { return *this->_iter; } + + inline typename Container::value_type getType(void) const { return *this->_iter; } +}; + /*}}}*/ +class PackageContainerInterface { /*{{{*/ +/** \class PackageContainerInterface + + * Interface ensuring that all operations can be executed on the yet to + * define concrete PackageContainer - access to all methods is possible, + * but in general the wrappers provided by the PackageContainer template + * are nicer to use. + + * This class mostly protects use from the need to write all implementation + * of the methods working on containers in the template */ +public: + template<class Itr> class iterator_base { /*{{{*/ + pkgCache::PkgIterator getType() const { return static_cast<Itr const*>(this)->getType(); }; + public: + operator pkgCache::PkgIterator(void) const { return getType(); } + + inline const char *Name() const {return getType().Name(); } + inline std::string FullName(bool const Pretty) const { return getType().FullName(Pretty); } + inline std::string FullName() const { return getType().FullName(); } + APT_DEPRECATED_MSG("Use the .Section method of VerIterator instead") inline const char *Section() const { + APT_IGNORE_DEPRECATED_PUSH + return getType().Section(); + APT_IGNORE_DEPRECATED_POP + } + inline bool Purge() const {return getType().Purge(); } + inline const char *Arch() const {return getType().Arch(); } + inline pkgCache::GrpIterator Group() const { return getType().Group(); } + inline pkgCache::VerIterator VersionList() const { return getType().VersionList(); } + inline pkgCache::VerIterator CurrentVer() const { return getType().CurrentVer(); } + inline pkgCache::DepIterator RevDependsList() const { return getType().RevDependsList(); } + inline pkgCache::PrvIterator ProvidesList() const { return getType().ProvidesList(); } + inline pkgCache::PkgIterator::OkState State() const { return getType().State(); } + APT_DEPRECATED_MSG("This method does not respect apt_preferences! Use pkgDepCache::GetCandidateVersion(Pkg)") inline const char *CandVersion() const { return getType().CandVersion(); } + inline const char *CurVersion() const { return getType().CurVersion(); } + inline pkgCache *Cache() const { return getType().Cache(); } + inline unsigned long Index() const {return getType().Index();} + // we have only valid iterators here + inline bool end() const { return false; } + + inline pkgCache::Package const * operator->() const {return &*getType();} + }; + /*}}}*/ + + virtual bool insert(pkgCache::PkgIterator const &P) = 0; + virtual bool empty() const = 0; + virtual void clear() = 0; + virtual size_t size() const = 0; + + enum APT_DEPRECATED_MSG("Use CacheSetHelper::PkgSelector instead") Constructor { UNKNOWN = CacheSetHelper::UNKNOWN, + REGEX = CacheSetHelper::REGEX, + TASK = CacheSetHelper::TASK, + FNMATCH = CacheSetHelper::FNMATCH }; +APT_IGNORE_DEPRECATED_PUSH + void setConstructor(Constructor const by) { ConstructedBy = (CacheSetHelper::PkgSelector)by; } +APT_IGNORE_DEPRECATED_POP + + void setConstructor(CacheSetHelper::PkgSelector const by) { ConstructedBy = by; } + CacheSetHelper::PkgSelector getConstructor() const { return ConstructedBy; } + PackageContainerInterface(); + explicit PackageContainerInterface(CacheSetHelper::PkgSelector const by); + PackageContainerInterface& operator=(PackageContainerInterface const &other); + virtual ~PackageContainerInterface(); + + APT_DEPRECATED_MSG("Use helper.PackageFrom(CacheSetHelper::TASK, …) instead") static bool FromTask(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern, CacheSetHelper &helper) { + return helper.PackageFrom(CacheSetHelper::TASK, pci, Cache, pattern); } + APT_DEPRECATED_MSG("Use helper.PackageFrom(CacheSetHelper::REGEX, …) instead") static bool FromRegEx(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern, CacheSetHelper &helper) { + return helper.PackageFrom(CacheSetHelper::REGEX, pci, Cache, pattern); } + APT_DEPRECATED_MSG("Use helper.PackageFrom(CacheSetHelper::FNMATCH, …) instead") static bool FromFnmatch(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern, CacheSetHelper &helper) { + return helper.PackageFrom(CacheSetHelper::FNMATCH, pci, Cache, pattern); } + APT_DEPRECATED_MSG("Use helper.PackageFrom(CacheSetHelper::PACKAGENAME, …) instead") static bool FromGroup(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string pattern, CacheSetHelper &helper) { + return helper.PackageFrom(CacheSetHelper::PACKAGENAME, pci, Cache, pattern); } + APT_DEPRECATED_MSG("Use helper.PackageFrom(CacheSetHelper::STRING, …) instead") static bool FromString(PackageContainerInterface * const pci, pkgCacheFile &Cache, std::string const &pattern, CacheSetHelper &helper) { + return helper.PackageFrom(CacheSetHelper::STRING, pci, Cache, pattern); } + APT_DEPRECATED_MSG("Use helper.PackageFromCommandLine instead") static bool FromCommandLine(PackageContainerInterface * const pci, pkgCacheFile &Cache, const char **cmdline, CacheSetHelper &helper) { + return helper.PackageFromCommandLine(pci, Cache, cmdline); } + + APT_DEPRECATED_MSG("enum moved to CacheSetHelper::PkgModifier") typedef CacheSetHelper::PkgModifier Modifier; + +APT_IGNORE_DEPRECATED_PUSH + APT_DEPRECATED_MSG("Use helper.PackageFromName instead") static pkgCache::PkgIterator FromName(pkgCacheFile &Cache, std::string const &pattern, CacheSetHelper &helper) { + return helper.PackageFromName(Cache, pattern); } + APT_DEPRECATED_MSG("Use helper.PackageFromModifierCommandLine instead") static bool FromModifierCommandLine(unsigned short &modID, PackageContainerInterface * const pci, + pkgCacheFile &Cache, const char * cmdline, + std::list<Modifier> const &mods, CacheSetHelper &helper) { + return helper.PackageFromModifierCommandLine(modID, pci, Cache, cmdline, mods); } +APT_IGNORE_DEPRECATED_POP + +private: + CacheSetHelper::PkgSelector ConstructedBy; + void * const d; +}; + /*}}}*/ +template<class Container> class PackageContainer : public PackageContainerInterface {/*{{{*/ +/** \class APT::PackageContainer + + Simple wrapper around a container class like std::set to provide a similar + interface to a set of packages as to the complete set of all packages in the + pkgCache. */ + Container _cont; +public: /*{{{*/ + /** \brief smell like a pkgCache::PkgIterator */ + typedef Container_const_iterator<PackageContainerInterface, Container, PackageContainer> const_iterator; + typedef Container_iterator<PackageContainerInterface, Container, PackageContainer> iterator; + typedef Container_const_reverse_iterator<PackageContainerInterface, Container, PackageContainer> const_reverse_iterator; + typedef Container_reverse_iterator<PackageContainerInterface, Container, PackageContainer> reverse_iterator; + typedef typename Container::value_type value_type; + typedef typename Container::pointer pointer; + typedef typename Container::const_pointer const_pointer; + typedef typename Container::reference reference; + typedef typename Container::const_reference const_reference; + typedef typename Container::difference_type difference_type; + typedef typename Container::size_type size_type; + typedef typename Container::allocator_type allocator_type; + + bool insert(pkgCache::PkgIterator const &P) APT_OVERRIDE { if (P.end() == true) return false; _cont.insert(P); return true; } + template<class Cont> void insert(PackageContainer<Cont> const &pkgcont) { _cont.insert((typename Cont::const_iterator)pkgcont.begin(), (typename Cont::const_iterator)pkgcont.end()); } + void insert(const_iterator begin, const_iterator end) { _cont.insert(begin, end); } + + bool empty() const APT_OVERRIDE { return _cont.empty(); } + void clear() APT_OVERRIDE { return _cont.clear(); } + size_t size() const APT_OVERRIDE { return _cont.size(); } +#if __GNUC__ >= 5 || (__GNUC_MINOR__ >= 9 && __GNUC__ >= 4) + iterator erase( const_iterator pos ) { return iterator(_cont.erase(pos._iter)); } + iterator erase( const_iterator first, const_iterator last ) { return iterator(_cont.erase(first._iter, last._iter)); } +#else + iterator erase( iterator pos ) { return iterator(_cont.erase(pos._iter)); } + iterator erase( iterator first, iterator last ) { return iterator(_cont.erase(first._iter, last._iter)); } +#endif + const_iterator begin() const { return const_iterator(_cont.begin()); } + const_iterator end() const { return const_iterator(_cont.end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(_cont.rbegin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(_cont.rend()); } +#if __cplusplus >= 201103L + const_iterator cbegin() const { return const_iterator(_cont.cbegin()); } + const_iterator cend() const { return const_iterator(_cont.cend()); } + const_reverse_iterator crbegin() const { return const_reverse_iterator(_cont.crbegin()); } + const_reverse_iterator crend() const { return const_reverse_iterator(_cont.crend()); } +#endif + iterator begin() { return iterator(_cont.begin()); } + iterator end() { return iterator(_cont.end()); } + reverse_iterator rbegin() { return reverse_iterator(_cont.rbegin()); } + reverse_iterator rend() { return reverse_iterator(_cont.rend()); } + const_iterator find(pkgCache::PkgIterator const &P) const { return const_iterator(_cont.find(P)); } + + PackageContainer() : PackageContainerInterface(CacheSetHelper::UNKNOWN) {} + explicit PackageContainer(CacheSetHelper::PkgSelector const &by) : PackageContainerInterface(by) {} +APT_IGNORE_DEPRECATED_PUSH + APT_DEPRECATED_MSG("Construct with a CacheSetHelper::PkgSelector instead") explicit PackageContainer(Constructor const &by) : PackageContainerInterface((CacheSetHelper::PkgSelector)by) {} +APT_IGNORE_DEPRECATED_POP + template<typename Itr> PackageContainer(Itr first, Itr last) : PackageContainerInterface(CacheSetHelper::UNKNOWN), _cont(first, last) {} +#if __cplusplus >= 201103L + PackageContainer(std::initializer_list<value_type> list) : PackageContainerInterface(CacheSetHelper::UNKNOWN), _cont(list) {} + void push_back(value_type&& P) { _cont.emplace_back(std::move(P)); } + template<typename... Args> void emplace_back(Args&&... args) { _cont.emplace_back(std::forward<Args>(args)...); } +#endif + void push_back(const value_type& P) { _cont.push_back(P); } + + /** \brief sort all included versions with given comparer + + Some containers are sorted by default, some are not and can't be, + but a few like std::vector can be sorted if need be, so this can be + specialized in later on. The default is that this will fail though. + Specifically, already sorted containers like std::set will return + false as well as there is no easy way to check that the given comparer + would sort in the same way the set is currently sorted + + \return \b true if the set was sorted, \b false if not. */ + template<class Compare> bool sort(Compare /*Comp*/) { return false; } + + /** \brief returns all packages in the cache who belong to the given task + + A simple helper responsible for search for all members of a task + in the cache. Optional it prints a notice about the + packages chosen cause of the given task. + \param Cache the packages are in + \param pattern name of the task + \param helper responsible for error and message handling */ + static PackageContainer FromTask(pkgCacheFile &Cache, std::string const &pattern, CacheSetHelper &helper) { + PackageContainer cont(CacheSetHelper::TASK); + helper.PackageFrom(CacheSetHelper::TASK, &cont, Cache, pattern); + return cont; + } + static PackageContainer FromTask(pkgCacheFile &Cache, std::string const &pattern) { + CacheSetHelper helper; + return FromTask(Cache, pattern, helper); + } + + /** \brief returns all packages in the cache whose name matches a given pattern + + A simple helper responsible for executing a regular expression on all + package names in the cache. Optional it prints a notice about the + packages chosen cause of the given package. + \param Cache the packages are in + \param pattern regular expression for package names + \param helper responsible for error and message handling */ + static PackageContainer FromRegEx(pkgCacheFile &Cache, std::string const &pattern, CacheSetHelper &helper) { + PackageContainer cont(CacheSetHelper::REGEX); + helper.PackageFrom(CacheSetHelper::REGEX, &cont, Cache, pattern); + return cont; + } + + static PackageContainer FromRegEx(pkgCacheFile &Cache, std::string const &pattern) { + CacheSetHelper helper; + return FromRegEx(Cache, pattern, helper); + } + + static PackageContainer FromFnmatch(pkgCacheFile &Cache, std::string const &pattern, CacheSetHelper &helper) { + PackageContainer cont(CacheSetHelper::FNMATCH); + helper.PackageFrom(CacheSetHelper::FNMATCH, &cont, Cache, pattern); + return cont; + } + static PackageContainer FromFnMatch(pkgCacheFile &Cache, std::string const &pattern) { + CacheSetHelper helper; + return FromFnmatch(Cache, pattern, helper); + } + +APT_IGNORE_DEPRECATED_PUSH + /** \brief returns a package specified by a string + + \param Cache the package is in + \param pattern String the package name should be extracted from + \param helper responsible for error and message handling */ + APT_DEPRECATED_MSG("Use helper.PackageFromName instead") static pkgCache::PkgIterator FromName(pkgCacheFile &Cache, std::string const &pattern, CacheSetHelper &helper) { + return helper.PackageFromName(Cache, pattern); + } + APT_DEPRECATED_MSG("Use helper.PackageFromName instead") static pkgCache::PkgIterator FromName(pkgCacheFile &Cache, std::string const &pattern) { + CacheSetHelper helper; + return FromName(Cache, pattern, helper); + } +APT_IGNORE_DEPRECATED_POP + + /** \brief returns all packages specified by a string + + \param Cache the packages are in + \param pattern String the package name(s) should be extracted from + \param helper responsible for error and message handling */ + static PackageContainer FromString(pkgCacheFile &Cache, std::string const &pattern, CacheSetHelper &helper) { + PackageContainer cont; + helper.PackageFrom(CacheSetHelper::PACKAGENAME, &cont, Cache, pattern); + return cont; + } + static PackageContainer FromString(pkgCacheFile &Cache, std::string const &pattern) { + CacheSetHelper helper; + return FromString(Cache, pattern, helper); + } + + /** \brief returns all packages specified on the commandline + + Get all package names from the commandline and executes regex's if needed. + No special package command is supported, just plain names. + \param Cache the packages are in + \param cmdline Command line the package names should be extracted from + \param helper responsible for error and message handling */ + static PackageContainer FromCommandLine(pkgCacheFile &Cache, const char **cmdline, CacheSetHelper &helper) { + PackageContainer cont; + helper.PackageFromCommandLine(&cont, Cache, cmdline); + return cont; + } + static PackageContainer FromCommandLine(pkgCacheFile &Cache, const char **cmdline) { + CacheSetHelper helper; + return FromCommandLine(Cache, cmdline, helper); + } + + /** \brief group packages by a action modifiers + + At some point it is needed to get from the same commandline + different package sets grouped by a modifier. Take + apt-get install apt awesome- + as an example. + \param Cache the packages are in + \param cmdline Command line the package names should be extracted from + \param mods list of modifiers the method should accept + \param fallback the default modifier group for a package + \param helper responsible for error and message handling */ + static std::map<unsigned short, PackageContainer> GroupedFromCommandLine( + pkgCacheFile &Cache, + const char **cmdline, + std::list<CacheSetHelper::PkgModifier> const &mods, + unsigned short const &fallback, + CacheSetHelper &helper) { + std::map<unsigned short, PackageContainer> pkgsets; + for (const char **I = cmdline; *I != 0; ++I) { + unsigned short modID = fallback; + PackageContainer pkgset; + helper.PackageFromModifierCommandLine(modID, &pkgset, Cache, *I, mods); + pkgsets[modID].insert(pkgset); + } + return pkgsets; + } + static std::map<unsigned short, PackageContainer> GroupedFromCommandLine( + pkgCacheFile &Cache, + const char **cmdline, + std::list<CacheSetHelper::PkgModifier> const &mods, + unsigned short const &fallback) { + CacheSetHelper helper; + return GroupedFromCommandLine(Cache, cmdline, + mods, fallback, helper); + } + /*}}}*/ +}; /*}}}*/ +// various specialisations for PackageContainer /*{{{*/ +template<> template<class Cont> void PackageContainer<std::list<pkgCache::PkgIterator> >::insert(PackageContainer<Cont> const &pkgcont) { + for (typename PackageContainer<Cont>::const_iterator p = pkgcont.begin(); p != pkgcont.end(); ++p) + _cont.push_back(*p); +} +#if __cplusplus >= 201103L +template<> template<class Cont> void PackageContainer<std::forward_list<pkgCache::PkgIterator> >::insert(PackageContainer<Cont> const &pkgcont) { + for (typename PackageContainer<Cont>::const_iterator p = pkgcont.begin(); p != pkgcont.end(); ++p) + _cont.push_front(*p); +} +#endif +template<> template<class Cont> void PackageContainer<std::deque<pkgCache::PkgIterator> >::insert(PackageContainer<Cont> const &pkgcont) { + for (typename PackageContainer<Cont>::const_iterator p = pkgcont.begin(); p != pkgcont.end(); ++p) + _cont.push_back(*p); +} +template<> template<class Cont> void PackageContainer<std::vector<pkgCache::PkgIterator> >::insert(PackageContainer<Cont> const &pkgcont) { + for (typename PackageContainer<Cont>::const_iterator p = pkgcont.begin(); p != pkgcont.end(); ++p) + _cont.push_back(*p); +} +// these are 'inline' as otherwise the linker has problems with seeing these untemplated +// specializations again and again - but we need to see them, so that library users can use them +template<> inline bool PackageContainer<std::list<pkgCache::PkgIterator> >::insert(pkgCache::PkgIterator const &P) { + if (P.end() == true) + return false; + _cont.push_back(P); + return true; +} +#if __cplusplus >= 201103L +template<> inline bool PackageContainer<std::forward_list<pkgCache::PkgIterator> >::insert(pkgCache::PkgIterator const &P) { + if (P.end() == true) + return false; + _cont.push_front(P); + return true; +} +#endif +template<> inline bool PackageContainer<std::deque<pkgCache::PkgIterator> >::insert(pkgCache::PkgIterator const &P) { + if (P.end() == true) + return false; + _cont.push_back(P); + return true; +} +template<> inline bool PackageContainer<std::vector<pkgCache::PkgIterator> >::insert(pkgCache::PkgIterator const &P) { + if (P.end() == true) + return false; + _cont.push_back(P); + return true; +} +template<> inline void PackageContainer<std::list<pkgCache::PkgIterator> >::insert(const_iterator begin, const_iterator end) { + for (const_iterator p = begin; p != end; ++p) + _cont.push_back(*p); +} +#if __cplusplus >= 201103L +template<> inline void PackageContainer<std::forward_list<pkgCache::PkgIterator> >::insert(const_iterator begin, const_iterator end) { + for (const_iterator p = begin; p != end; ++p) + _cont.push_front(*p); +} +#endif +template<> inline void PackageContainer<std::deque<pkgCache::PkgIterator> >::insert(const_iterator begin, const_iterator end) { + for (const_iterator p = begin; p != end; ++p) + _cont.push_back(*p); +} +template<> inline void PackageContainer<std::vector<pkgCache::PkgIterator> >::insert(const_iterator begin, const_iterator end) { + for (const_iterator p = begin; p != end; ++p) + _cont.push_back(*p); +} +#if APT_GCC_VERSION < 0x409 +template<> inline PackageContainer<std::set<pkgCache::PkgIterator> >::iterator PackageContainer<std::set<pkgCache::PkgIterator> >::erase(iterator i) { + _cont.erase(i._iter); + return end(); +} +template<> inline PackageContainer<std::set<pkgCache::PkgIterator> >::iterator PackageContainer<std::set<pkgCache::PkgIterator> >::erase(iterator first, iterator last) { + _cont.erase(first, last); + return end(); +} +#endif +template<> template<class Compare> inline bool PackageContainer<std::vector<pkgCache::PkgIterator> >::sort(Compare Comp) { + std::sort(_cont.begin(), _cont.end(), Comp); + return true; +} +template<> template<class Compare> inline bool PackageContainer<std::list<pkgCache::PkgIterator> >::sort(Compare Comp) { + _cont.sort(Comp); + return true; +} +#if __cplusplus >= 201103L +template<> template<class Compare> inline bool PackageContainer<std::forward_list<pkgCache::PkgIterator> >::sort(Compare Comp) { + _cont.sort(Comp); + return true; +} +#endif +template<> template<class Compare> inline bool PackageContainer<std::deque<pkgCache::PkgIterator> >::sort(Compare Comp) { + std::sort(_cont.begin(), _cont.end(), Comp); + return true; +} + /*}}}*/ + +// class PackageUniverse - pkgCache as PackageContainerInterface /*{{{*/ +/** \class PackageUniverse + + Wraps around our usual pkgCache, so that it can be stuffed into methods + expecting a PackageContainer. + + The wrapping is read-only in practice modeled by making erase and co + private methods. */ +class APT_PUBLIC PackageUniverse : public PackageContainerInterface { + pkgCache * const _cont; + void * const d; +public: + class const_iterator : public APT::Container_iterator_base<APT::PackageContainerInterface, PackageUniverse, PackageUniverse::const_iterator, pkgCache::PkgIterator, pkgCache::PkgIterator> + { + public: + explicit const_iterator(pkgCache::PkgIterator i): + Container_iterator_base<APT::PackageContainerInterface, PackageUniverse, PackageUniverse::const_iterator, pkgCache::PkgIterator, pkgCache::PkgIterator>(i) {} + + inline pkgCache::PkgIterator getType(void) const { return _iter; } + }; + typedef const_iterator iterator; + typedef pkgCache::PkgIterator value_type; + typedef typename pkgCache::PkgIterator* pointer; + typedef typename pkgCache::PkgIterator const* const_pointer; + typedef const pkgCache::PkgIterator& const_reference; + typedef const_reference reference; + typedef const_iterator::difference_type difference_type; + typedef std::make_unsigned<const_iterator::difference_type>::type size_type; + + + bool empty() const APT_OVERRIDE { return false; } + size_t size() const APT_OVERRIDE { return _cont->Head().PackageCount; } + + const_iterator begin() const { return const_iterator(_cont->PkgBegin()); } + const_iterator end() const { return const_iterator(_cont->PkgEnd()); } + const_iterator cbegin() const { return const_iterator(_cont->PkgBegin()); } + const_iterator cend() const { return const_iterator(_cont->PkgEnd()); } + iterator begin() { return iterator(_cont->PkgBegin()); } + iterator end() { return iterator(_cont->PkgEnd()); } + + pkgCache * data() const { return _cont; } + + explicit PackageUniverse(pkgCache * const Owner); + explicit PackageUniverse(pkgCacheFile * const Owner); + virtual ~PackageUniverse(); + +private: + APT_HIDDEN bool insert(pkgCache::PkgIterator const &) APT_OVERRIDE { return true; } + template<class Cont> APT_HIDDEN void insert(PackageContainer<Cont> const &) { } + APT_HIDDEN void insert(const_iterator, const_iterator) { } + + APT_HIDDEN void clear() APT_OVERRIDE { } + APT_HIDDEN iterator erase( const_iterator pos ); + APT_HIDDEN iterator erase( const_iterator first, const_iterator last ); +}; + /*}}}*/ +typedef PackageContainer<std::set<pkgCache::PkgIterator> > PackageSet; +#if __cplusplus >= 201103L +typedef PackageContainer<std::unordered_set<pkgCache::PkgIterator> > PackageUnorderedSet; +typedef PackageContainer<std::forward_list<pkgCache::PkgIterator> > PackageForwardList; +#endif +typedef PackageContainer<std::list<pkgCache::PkgIterator> > PackageList; +typedef PackageContainer<std::deque<pkgCache::PkgIterator> > PackageDeque; +typedef PackageContainer<std::vector<pkgCache::PkgIterator> > PackageVector; + +class VersionContainerInterface { /*{{{*/ +/** \class APT::VersionContainerInterface + + Same as APT::PackageContainerInterface, just for Versions */ +public: + /** \brief smell like a pkgCache::VerIterator */ + template<class Itr> class iterator_base { /*{{{*/ + pkgCache::VerIterator getType() const { return static_cast<Itr const*>(this)->getType(); }; + public: + operator pkgCache::VerIterator(void) { return getType(); } + + inline pkgCache *Cache() const { return getType().Cache(); } + inline unsigned long Index() const {return getType().Index();} + inline int CompareVer(const pkgCache::VerIterator &B) const { return getType().CompareVer(B); } + inline const char *VerStr() const { return getType().VerStr(); } + inline const char *Section() const { return getType().Section(); } + inline const char *Arch() const { return getType().Arch(); } + inline pkgCache::PkgIterator ParentPkg() const { return getType().ParentPkg(); } + inline pkgCache::DescIterator DescriptionList() const { return getType().DescriptionList(); } + inline pkgCache::DescIterator TranslatedDescription() const { return getType().TranslatedDescription(); } + inline pkgCache::DepIterator DependsList() const { return getType().DependsList(); } + inline pkgCache::PrvIterator ProvidesList() const { return getType().ProvidesList(); } + inline pkgCache::VerFileIterator FileList() const { return getType().FileList(); } + inline bool Downloadable() const { return getType().Downloadable(); } + inline const char *PriorityType() const { return getType().PriorityType(); } + inline std::string RelStr() const { return getType().RelStr(); } + inline bool Automatic() const { return getType().Automatic(); } + inline pkgCache::VerFileIterator NewestFile() const { return getType().NewestFile(); } + // we have only valid iterators here + inline bool end() const { return false; } + + inline pkgCache::Version const * operator->() const { return &*getType(); } + }; + /*}}}*/ + + virtual bool insert(pkgCache::VerIterator const &V) = 0; + virtual bool empty() const = 0; + virtual void clear() = 0; + virtual size_t size() const = 0; + + /** \brief specifies which version(s) will be returned if non is given */ + enum APT_DEPRECATED_MSG("enum moved to CacheSetHelper::VerSelector instead") Version { + ALL = CacheSetHelper::ALL, + CANDANDINST = CacheSetHelper::CANDANDINST, + CANDIDATE = CacheSetHelper::CANDIDATE, + INSTALLED = CacheSetHelper::INSTALLED, + CANDINST = CacheSetHelper::CANDINST, + INSTCAND = CacheSetHelper::INSTCAND, + NEWEST = CacheSetHelper::NEWEST + }; + + struct Modifier { + unsigned short const ID; + const char * const Alias; + enum Position { NONE, PREFIX, POSTFIX } const Pos; + enum CacheSetHelper::VerSelector const SelectVersion; + Modifier (unsigned short const &id, const char * const alias, Position const &pos, + enum CacheSetHelper::VerSelector const select) : ID(id), Alias(alias), Pos(pos), + SelectVersion(select) {} +APT_IGNORE_DEPRECATED_PUSH + APT_DEPRECATED_MSG("Construct with a CacheSetHelper::VerSelector instead") Modifier(unsigned short const &id, const char * const alias, Position const &pos, + Version const &select) : ID(id), Alias(alias), Pos(pos), + SelectVersion((CacheSetHelper::VerSelector)select) {} +APT_IGNORE_DEPRECATED_POP + }; + + static bool FromCommandLine(VersionContainerInterface * const vci, pkgCacheFile &Cache, + const char **cmdline, CacheSetHelper::VerSelector const fallback, + CacheSetHelper &helper); +APT_IGNORE_DEPRECATED_PUSH + APT_DEPRECATED_MSG("Use CacheSetHelper::VerSelector as fallback selector") static bool FromCommandLine(VersionContainerInterface * const vci, pkgCacheFile &Cache, + const char **cmdline, Version const &fallback, + CacheSetHelper &helper) { + return FromCommandLine(vci, Cache, cmdline, (CacheSetHelper::VerSelector)fallback, helper); + } +APT_IGNORE_DEPRECATED_POP + + static bool FromString(VersionContainerInterface * const vci, pkgCacheFile &Cache, + std::string pkg, CacheSetHelper::VerSelector const fallback, CacheSetHelper &helper, + bool const onlyFromName = false); +APT_IGNORE_DEPRECATED_PUSH + APT_DEPRECATED_MSG("Use CacheSetHelper::VerSelector as fallback selector") static bool FromString(VersionContainerInterface * const vci, pkgCacheFile &Cache, + std::string pkg, Version const &fallback, CacheSetHelper &helper, + bool const onlyFromName = false) { + return FromString(vci, Cache, pkg, (CacheSetHelper::VerSelector)fallback, helper, onlyFromName); + } +APT_IGNORE_DEPRECATED_POP + + static bool FromPackage(VersionContainerInterface * const vci, pkgCacheFile &Cache, + pkgCache::PkgIterator const &P, CacheSetHelper::VerSelector const fallback, + CacheSetHelper &helper); +APT_IGNORE_DEPRECATED_PUSH + APT_DEPRECATED_MSG("Use CacheSetHelper::VerSelector as fallback selector") static bool FromPackage(VersionContainerInterface * const vci, pkgCacheFile &Cache, + pkgCache::PkgIterator const &P, Version const &fallback, + CacheSetHelper &helper) { + return FromPackage(vci, Cache, P, (CacheSetHelper::VerSelector)fallback, helper); + } +APT_IGNORE_DEPRECATED_POP + + static bool FromModifierCommandLine(unsigned short &modID, + VersionContainerInterface * const vci, + pkgCacheFile &Cache, const char * cmdline, + std::list<Modifier> const &mods, + CacheSetHelper &helper); + + + static bool FromDependency(VersionContainerInterface * const vci, + pkgCacheFile &Cache, + pkgCache::DepIterator const &D, + CacheSetHelper::VerSelector const selector, + CacheSetHelper &helper); +APT_IGNORE_DEPRECATED_PUSH + APT_DEPRECATED_MSG("Use CacheSetHelper::VerSelector as fallback selector") static bool FromDependency(VersionContainerInterface * const vci, + pkgCacheFile &Cache, + pkgCache::DepIterator const &D, + Version const &selector, + CacheSetHelper &helper) { + return FromDependency(vci, Cache, D, (CacheSetHelper::VerSelector)selector, helper); + } +APT_IGNORE_DEPRECATED_POP + + VersionContainerInterface(); + VersionContainerInterface& operator=(VersionContainerInterface const &other); + virtual ~VersionContainerInterface(); +private: + void * const d; + +protected: /*{{{*/ + + /** \brief returns the candidate version of the package + + \param Cache to be used to query for information + \param Pkg we want the candidate version from this package + \param helper used in this container instance */ + static pkgCache::VerIterator getCandidateVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg, CacheSetHelper &helper); + + /** \brief returns the installed version of the package + + \param Cache to be used to query for information + \param Pkg we want the installed version from this package + \param helper used in this container instance */ + static pkgCache::VerIterator getInstalledVer(pkgCacheFile &Cache, + pkgCache::PkgIterator const &Pkg, CacheSetHelper &helper); + /*}}}*/ +}; + /*}}}*/ +template<class Container> class VersionContainer : public VersionContainerInterface {/*{{{*/ +/** \class APT::VersionContainer + + Simple wrapper around a container class like std::set to provide a similar + interface to a set of versions as to the complete set of all versions in the + pkgCache. */ + Container _cont; +public: /*{{{*/ + + typedef Container_const_iterator<VersionContainerInterface, Container, VersionContainer> const_iterator; + typedef Container_iterator<VersionContainerInterface, Container, VersionContainer> iterator; + typedef Container_const_reverse_iterator<VersionContainerInterface, Container, VersionContainer> const_reverse_iterator; + typedef Container_reverse_iterator<VersionContainerInterface, Container, VersionContainer> reverse_iterator; + typedef typename Container::value_type value_type; + typedef typename Container::pointer pointer; + typedef typename Container::const_pointer const_pointer; + typedef typename Container::reference reference; + typedef typename Container::const_reference const_reference; + typedef typename Container::difference_type difference_type; + typedef typename Container::size_type size_type; + typedef typename Container::allocator_type allocator_type; + + bool insert(pkgCache::VerIterator const &V) APT_OVERRIDE { if (V.end() == true) return false; _cont.insert(V); return true; } + template<class Cont> void insert(VersionContainer<Cont> const &vercont) { _cont.insert((typename Cont::const_iterator)vercont.begin(), (typename Cont::const_iterator)vercont.end()); } + void insert(const_iterator begin, const_iterator end) { _cont.insert(begin, end); } + bool empty() const APT_OVERRIDE { return _cont.empty(); } + void clear() APT_OVERRIDE { return _cont.clear(); } + size_t size() const APT_OVERRIDE { return _cont.size(); } +#if APT_GCC_VERSION >= 0x409 + iterator erase( const_iterator pos ) { return iterator(_cont.erase(pos._iter)); } + iterator erase( const_iterator first, const_iterator last ) { return iterator(_cont.erase(first._iter, last._iter)); } +#else + iterator erase( iterator pos ) { return iterator(_cont.erase(pos._iter)); } + iterator erase( iterator first, iterator last ) { return iterator(_cont.erase(first._iter, last._iter)); } +#endif + const_iterator begin() const { return const_iterator(_cont.begin()); } + const_iterator end() const { return const_iterator(_cont.end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(_cont.rbegin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(_cont.rend()); } +#if __cplusplus >= 201103L + const_iterator cbegin() const { return const_iterator(_cont.cbegin()); } + const_iterator cend() const { return const_iterator(_cont.cend()); } + const_reverse_iterator crbegin() const { return const_reverse_iterator(_cont.crbegin()); } + const_reverse_iterator crend() const { return const_reverse_iterator(_cont.crend()); } +#endif + iterator begin() { return iterator(_cont.begin()); } + iterator end() { return iterator(_cont.end()); } + reverse_iterator rbegin() { return reverse_iterator(_cont.rbegin()); } + reverse_iterator rend() { return reverse_iterator(_cont.rend()); } + const_iterator find(pkgCache::VerIterator const &V) const { return const_iterator(_cont.find(V)); } + + VersionContainer() : VersionContainerInterface() {} + template<typename Itr> VersionContainer(Itr first, Itr last) : VersionContainerInterface(), _cont(first, last) {} +#if __cplusplus >= 201103L + VersionContainer(std::initializer_list<value_type> list) : VersionContainerInterface(), _cont(list) {} + void push_back(value_type&& P) { _cont.emplace_back(std::move(P)); } + template<typename... Args> void emplace_back(Args&&... args) { _cont.emplace_back(std::forward<Args>(args)...); } +#endif + void push_back(const value_type& P) { _cont.push_back(P); } + + /** \brief sort all included versions with given comparer + + Some containers are sorted by default, some are not and can't be, + but a few like std::vector can be sorted if need be, so this can be + specialized in later on. The default is that this will fail though. + Specifically, already sorted containers like std::set will return + false as well as there is no easy way to check that the given comparer + would sort in the same way the set is currently sorted + + \return \b true if the set was sorted, \b false if not. */ + template<class Compare> bool sort(Compare /*Comp*/) { return false; } + + /** \brief returns all versions specified on the commandline + + Get all versions from the commandline, uses given default version if + non specifically requested and executes regex's if needed on names. + \param Cache the packages and versions are in + \param cmdline Command line the versions should be extracted from + \param fallback version specification + \param helper responsible for error and message handling */ + static VersionContainer FromCommandLine(pkgCacheFile &Cache, const char **cmdline, + CacheSetHelper::VerSelector const fallback, CacheSetHelper &helper) { + VersionContainer vercon; + VersionContainerInterface::FromCommandLine(&vercon, Cache, cmdline, fallback, helper); + return vercon; + } + static VersionContainer FromCommandLine(pkgCacheFile &Cache, const char **cmdline, + CacheSetHelper::VerSelector const fallback) { + CacheSetHelper helper; + return FromCommandLine(Cache, cmdline, fallback, helper); + } + static VersionContainer FromCommandLine(pkgCacheFile &Cache, const char **cmdline) { + return FromCommandLine(Cache, cmdline, CacheSetHelper::CANDINST); + } + static VersionContainer FromString(pkgCacheFile &Cache, std::string const &pkg, + CacheSetHelper::VerSelector const fallback, CacheSetHelper &helper, + bool const /*onlyFromName = false*/) { + VersionContainer vercon; + VersionContainerInterface::FromString(&vercon, Cache, pkg, fallback, helper); + return vercon; + } + static VersionContainer FromString(pkgCacheFile &Cache, std::string pkg, + CacheSetHelper::VerSelector const fallback) { + CacheSetHelper helper; + return FromString(Cache, pkg, fallback, helper); + } + static VersionContainer FromString(pkgCacheFile &Cache, std::string pkg) { + return FromString(Cache, pkg, CacheSetHelper::CANDINST); + } +APT_IGNORE_DEPRECATED_PUSH + static VersionContainer FromCommandLine(pkgCacheFile &Cache, const char **cmdline, + Version const &fallback, CacheSetHelper &helper) { + VersionContainer vercon; + VersionContainerInterface::FromCommandLine(&vercon, Cache, cmdline, (CacheSetHelper::VerSelector)fallback, helper); + return vercon; + } + static VersionContainer FromCommandLine(pkgCacheFile &Cache, const char **cmdline, + Version const &fallback) { + CacheSetHelper helper; + return FromCommandLine(Cache, cmdline, (CacheSetHelper::VerSelector)fallback, helper); + } + static VersionContainer FromString(pkgCacheFile &Cache, std::string const &pkg, + Version const &fallback, CacheSetHelper &helper, + bool const /*onlyFromName = false*/) { + VersionContainer vercon; + VersionContainerInterface::FromString(&vercon, Cache, pkg, (CacheSetHelper::VerSelector)fallback, helper); + return vercon; + } + static VersionContainer FromString(pkgCacheFile &Cache, std::string pkg, + Version const &fallback) { + CacheSetHelper helper; + return FromString(Cache, pkg, (CacheSetHelper::VerSelector)fallback, helper); + } +APT_IGNORE_DEPRECATED_POP + + /** \brief returns all versions specified for the package + + \param Cache the package and versions are in + \param P the package in question + \param fallback the version(s) you want to get + \param helper the helper used for display and error handling */ + static VersionContainer FromPackage(pkgCacheFile &Cache, pkgCache::PkgIterator const &P, + CacheSetHelper::VerSelector const fallback, CacheSetHelper &helper) { + VersionContainer vercon; + VersionContainerInterface::FromPackage(&vercon, Cache, P, fallback, helper); + return vercon; + } + static VersionContainer FromPackage(pkgCacheFile &Cache, pkgCache::PkgIterator const &P, + CacheSetHelper::VerSelector const fallback) { + CacheSetHelper helper; + return FromPackage(Cache, P, fallback, helper); + } +APT_IGNORE_DEPRECATED_PUSH + static VersionContainer FromPackage(pkgCacheFile &Cache, pkgCache::PkgIterator const &P, + Version const &fallback, CacheSetHelper &helper) { + VersionContainer vercon; + VersionContainerInterface::FromPackage(&vercon, Cache, P, (CacheSetHelper::VerSelector)fallback, helper); + return vercon; + } + static VersionContainer FromPackage(pkgCacheFile &Cache, pkgCache::PkgIterator const &P, + Version const &fallback) { + CacheSetHelper helper; + return FromPackage(Cache, P, (CacheSetHelper::VerSelector)fallback, helper); + } +APT_IGNORE_DEPRECATED_POP + static VersionContainer FromPackage(pkgCacheFile &Cache, pkgCache::PkgIterator const &P) { + return FromPackage(Cache, P, CacheSetHelper::CANDIDATE); + } + + static std::map<unsigned short, VersionContainer> GroupedFromCommandLine( + pkgCacheFile &Cache, + const char **cmdline, + std::list<Modifier> const &mods, + unsigned short const fallback, + CacheSetHelper &helper) { + std::map<unsigned short, VersionContainer> versets; + for (const char **I = cmdline; *I != 0; ++I) { + unsigned short modID = fallback; + VersionContainer verset; + VersionContainerInterface::FromModifierCommandLine(modID, &verset, Cache, *I, mods, helper); + versets[modID].insert(verset); + } + return versets; + + } + static std::map<unsigned short, VersionContainer> GroupedFromCommandLine( + pkgCacheFile &Cache, const char **cmdline, + std::list<Modifier> const &mods, + unsigned short const fallback) { + CacheSetHelper helper; + return GroupedFromCommandLine(Cache, cmdline, + mods, fallback, helper); + } + + static VersionContainer FromDependency(pkgCacheFile &Cache, pkgCache::DepIterator const &D, + CacheSetHelper::VerSelector const selector, CacheSetHelper &helper) { + VersionContainer vercon; + VersionContainerInterface::FromDependency(&vercon, Cache, D, selector, helper); + return vercon; + } + static VersionContainer FromDependency(pkgCacheFile &Cache, pkgCache::DepIterator const &D, + CacheSetHelper::VerSelector const selector) { + CacheSetHelper helper; + return FromDependency(Cache, D, selector, helper); + } +APT_IGNORE_DEPRECATED_PUSH + static VersionContainer FromDependency(pkgCacheFile &Cache, pkgCache::DepIterator const &D, + Version const &selector, CacheSetHelper &helper) { + VersionContainer vercon; + VersionContainerInterface::FromDependency(&vercon, Cache, D, (CacheSetHelper::VerSelector)selector, helper); + return vercon; + } + static VersionContainer FromDependency(pkgCacheFile &Cache, pkgCache::DepIterator const &D, + Version const &selector) { + CacheSetHelper helper; + return FromDependency(Cache, D, (CacheSetHelper::VerSelector)selector, helper); + } +APT_IGNORE_DEPRECATED_POP + static VersionContainer FromDependency(pkgCacheFile &Cache, pkgCache::DepIterator const &D) { + return FromDependency(Cache, D, CacheSetHelper::CANDIDATE); + } + /*}}}*/ +}; /*}}}*/ +// various specialisations for VersionContainer /*{{{*/ +template<> template<class Cont> void VersionContainer<std::list<pkgCache::VerIterator> >::insert(VersionContainer<Cont> const &vercont) { + for (typename VersionContainer<Cont>::const_iterator v = vercont.begin(); v != vercont.end(); ++v) + _cont.push_back(*v); +} +#if __cplusplus >= 201103L +template<> template<class Cont> void VersionContainer<std::forward_list<pkgCache::VerIterator> >::insert(VersionContainer<Cont> const &vercont) { + for (typename VersionContainer<Cont>::const_iterator v = vercont.begin(); v != vercont.end(); ++v) + _cont.push_front(*v); +} +#endif +template<> template<class Cont> void VersionContainer<std::deque<pkgCache::VerIterator> >::insert(VersionContainer<Cont> const &vercont) { + for (typename VersionContainer<Cont>::const_iterator v = vercont.begin(); v != vercont.end(); ++v) + _cont.push_back(*v); +} +template<> template<class Cont> void VersionContainer<std::vector<pkgCache::VerIterator> >::insert(VersionContainer<Cont> const &vercont) { + for (typename VersionContainer<Cont>::const_iterator v = vercont.begin(); v != vercont.end(); ++v) + _cont.push_back(*v); +} +// these are 'inline' as otherwise the linker has problems with seeing these untemplated +// specializations again and again - but we need to see them, so that library users can use them +template<> inline bool VersionContainer<std::list<pkgCache::VerIterator> >::insert(pkgCache::VerIterator const &V) { + if (V.end() == true) + return false; + _cont.push_back(V); + return true; +} +#if __cplusplus >= 201103L +template<> inline bool VersionContainer<std::forward_list<pkgCache::VerIterator> >::insert(pkgCache::VerIterator const &V) { + if (V.end() == true) + return false; + _cont.push_front(V); + return true; +} +#endif +template<> inline bool VersionContainer<std::deque<pkgCache::VerIterator> >::insert(pkgCache::VerIterator const &V) { + if (V.end() == true) + return false; + _cont.push_back(V); + return true; +} +template<> inline bool VersionContainer<std::vector<pkgCache::VerIterator> >::insert(pkgCache::VerIterator const &V) { + if (V.end() == true) + return false; + _cont.push_back(V); + return true; +} +template<> inline void VersionContainer<std::list<pkgCache::VerIterator> >::insert(const_iterator begin, const_iterator end) { + for (const_iterator v = begin; v != end; ++v) + _cont.push_back(*v); +} +#if __cplusplus >= 201103L +template<> inline void VersionContainer<std::forward_list<pkgCache::VerIterator> >::insert(const_iterator begin, const_iterator end) { + for (const_iterator v = begin; v != end; ++v) + _cont.push_front(*v); +} +#endif +template<> inline void VersionContainer<std::deque<pkgCache::VerIterator> >::insert(const_iterator begin, const_iterator end) { + for (const_iterator v = begin; v != end; ++v) + _cont.push_back(*v); +} +template<> inline void VersionContainer<std::vector<pkgCache::VerIterator> >::insert(const_iterator begin, const_iterator end) { + for (const_iterator v = begin; v != end; ++v) + _cont.push_back(*v); +} +#if APT_GCC_VERSION < 0x409 +template<> inline VersionContainer<std::set<pkgCache::VerIterator> >::iterator VersionContainer<std::set<pkgCache::VerIterator> >::erase(iterator i) { + _cont.erase(i._iter); + return end(); +} +template<> inline VersionContainer<std::set<pkgCache::VerIterator> >::iterator VersionContainer<std::set<pkgCache::VerIterator> >::erase(iterator first, iterator last) { + _cont.erase(first, last); + return end(); +} +#endif +template<> template<class Compare> inline bool VersionContainer<std::vector<pkgCache::VerIterator> >::sort(Compare Comp) { + std::sort(_cont.begin(), _cont.end(), Comp); + return true; +} +template<> template<class Compare> inline bool VersionContainer<std::list<pkgCache::VerIterator> >::sort(Compare Comp) { + _cont.sort(Comp); + return true; +} +#if __cplusplus >= 201103L +template<> template<class Compare> inline bool VersionContainer<std::forward_list<pkgCache::VerIterator> >::sort(Compare Comp) { + _cont.sort(Comp); + return true; +} +#endif +template<> template<class Compare> inline bool VersionContainer<std::deque<pkgCache::VerIterator> >::sort(Compare Comp) { + std::sort(_cont.begin(), _cont.end(), Comp); + return true; +} + /*}}}*/ + +typedef VersionContainer<std::set<pkgCache::VerIterator> > VersionSet; +#if __cplusplus >= 201103L +typedef VersionContainer<std::unordered_set<pkgCache::VerIterator> > VersionUnorderedSet; +typedef VersionContainer<std::forward_list<pkgCache::VerIterator> > VersionForwardList; +#endif +typedef VersionContainer<std::list<pkgCache::VerIterator> > VersionList; +typedef VersionContainer<std::deque<pkgCache::VerIterator> > VersionDeque; +typedef VersionContainer<std::vector<pkgCache::VerIterator> > VersionVector; +} +#endif diff --git a/apt-pkg/cdrom.cc b/apt-pkg/cdrom.cc new file mode 100644 index 0000000..151608b --- /dev/null +++ b/apt-pkg/cdrom.cc @@ -0,0 +1,1000 @@ +/* + */ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/cdrom.h> +#include <apt-pkg/cdromutl.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/indexcopy.h> +#include <apt-pkg/strutl.h> + +#include <algorithm> +#include <fstream> +#include <iostream> +#include <iostream> +#include <sstream> +#include <string> +#include <vector> +#include <dirent.h> +#include <dlfcn.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <apti18n.h> + +#ifdef HAVE_UDEV +#include <libudev.h> +#endif + +using namespace std; + +// FindPackages - Find the package files on the CDROM /*{{{*/ +// --------------------------------------------------------------------- +/* We look over the cdrom for package files. This is a recursive + search that short circuits when it his a package file in the dir. + This speeds it up greatly as the majority of the size is in the + binary-* sub dirs. */ +bool pkgCdrom::FindPackages(string CD, + vector<string> &List, + vector<string> &SList, + vector<string> &SigList, + vector<string> &TransList, + string &InfoDir, pkgCdromStatus *log, + unsigned int Depth) +{ + static ino_t Inodes[9]; + DIR *D; + + // if we have a look we "pulse" now + if(log) + log->Update(); + + if (Depth >= 7) + return true; + + if (CD[CD.length()-1] != '/') + CD += '/'; + + if (chdir(CD.c_str()) != 0) + return _error->Errno("chdir","Unable to change to %s",CD.c_str()); + + // Look for a .disk subdirectory + if (InfoDir.empty() == true) + { + if (DirectoryExists(".disk") == true) + InfoDir = InfoDir + CD + ".disk/"; + } + + // Don't look into directories that have been marked to ignore. + if (RealFileExists(".aptignr") == true) + return true; + + /* Check _first_ for a signature file as apt-cdrom assumes that all files + under a Packages/Source file are in control of that file and stops + the scanning + */ + if (RealFileExists("Release.gpg") == true || RealFileExists("InRelease") == true) + { + SigList.push_back(CD); + } + + /* Aha! We found some package files. We assume that everything under + this dir is controlled by those package files so we don't look down + anymore */ + std::vector<APT::Configuration::Compressor> const compressor = APT::Configuration::getCompressors(); + for (std::vector<APT::Configuration::Compressor>::const_iterator c = compressor.begin(); + c != compressor.end(); ++c) + { + if (RealFileExists(std::string("Packages").append(c->Extension).c_str()) == false) + continue; + + if (_config->FindB("Debug::aptcdrom",false) == true) + std::clog << "Found Packages in " << CD << std::endl; + List.push_back(CD); + + // Continue down if thorough is given + if (_config->FindB("APT::CDROM::Thorough",false) == false) + return true; + break; + } + for (std::vector<APT::Configuration::Compressor>::const_iterator c = compressor.begin(); + c != compressor.end(); ++c) + { + if (RealFileExists(std::string("Sources").append(c->Extension).c_str()) == false) + continue; + + if (_config->FindB("Debug::aptcdrom",false) == true) + std::clog << "Found Sources in " << CD << std::endl; + SList.push_back(CD); + + // Continue down if thorough is given + if (_config->FindB("APT::CDROM::Thorough",false) == false) + return true; + break; + } + + // see if we find translation indices + if (DirectoryExists("i18n") == true) + { + D = opendir("i18n"); + for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D)) + { + if(strncmp(Dir->d_name, "Translation-", strlen("Translation-")) != 0) + continue; + string file = Dir->d_name; + for (std::vector<APT::Configuration::Compressor>::const_iterator c = compressor.begin(); + c != compressor.end(); ++c) + { + string fileext = flExtension(file); + if (file == fileext) + fileext.clear(); + else if (fileext.empty() == false) + fileext = "." + fileext; + + if (c->Extension == fileext) + { + if (_config->FindB("Debug::aptcdrom",false) == true) + std::clog << "Found translation " << Dir->d_name << " in " << CD << "i18n/" << std::endl; + file.erase(file.size() - fileext.size()); + TransList.push_back(CD + "i18n/" + file); + break; + } + } + } + closedir(D); + } + + D = opendir("."); + if (D == 0) + return _error->Errno("opendir","Unable to read %s",CD.c_str()); + + // Run over the directory + for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D)) + { + // Skip some files.. + if (strcmp(Dir->d_name,".") == 0 || + strcmp(Dir->d_name,"..") == 0 || + strcmp(Dir->d_name,".disk") == 0 || + strcmp(Dir->d_name,"debian-installer") == 0) + continue; + + // See if the name is a sub directory + struct stat Buf; + if (stat(Dir->d_name,&Buf) != 0) + continue; + + if (S_ISDIR(Buf.st_mode) == 0) + continue; + + unsigned int I; + for (I = 0; I != Depth; I++) + if (Inodes[I] == Buf.st_ino) + break; + if (I != Depth) + continue; + + // Store the inodes weve seen + Inodes[Depth] = Buf.st_ino; + + // Descend + if (FindPackages(CD + Dir->d_name,List,SList,SigList,TransList,InfoDir,log,Depth+1) == false) + break; + + if (chdir(CD.c_str()) != 0) + { + _error->Errno("chdir","Unable to change to %s", CD.c_str()); + closedir(D); + return false; + } + }; + + closedir(D); + + return !_error->PendingError(); +} + /*}}}*/ +// Score - We compute a 'score' for a path /*{{{*/ +// --------------------------------------------------------------------- +/* Paths are scored based on how close they come to what I consider + normal. That is ones that have 'dist' 'stable' 'testing' will score + higher than ones without. */ +int pkgCdrom::Score(string Path) +{ + int Res = 0; + if (Path.find("stable/") != string::npos) + Res += 29; + if (Path.find("/binary-") != string::npos) + Res += 20; + if (Path.find("testing/") != string::npos) + Res += 28; + if (Path.find("unstable/") != string::npos) + Res += 27; + if (Path.find("/dists/") != string::npos) + Res += 40; + if (Path.find("/main/") != string::npos) + Res += 20; + if (Path.find("/contrib/") != string::npos) + Res += 20; + if (Path.find("/non-free/") != string::npos) + Res += 20; + if (Path.find("/non-US/") != string::npos) + Res += 20; + if (Path.find("/source/") != string::npos) + Res += 10; + if (Path.find("/debian/") != string::npos) + Res -= 10; + + // check for symlinks in the patch leading to the actual file + // a symlink gets a big penalty + struct stat Buf; + string statPath = flNotFile(Path); + string cdromPath = _config->FindDir("Acquire::cdrom::mount"); + while(statPath != cdromPath && statPath != "./") { + statPath.resize(statPath.size()-1); // remove the trailing '/' + if (lstat(statPath.c_str(),&Buf) == 0) { + if(S_ISLNK(Buf.st_mode)) { + Res -= 60; + break; + } + } + statPath = flNotFile(statPath); // descent + } + + return Res; +} + /*}}}*/ +// DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/ +// --------------------------------------------------------------------- +/* Here we drop everything that is not this machines arch */ +bool pkgCdrom::DropBinaryArch(vector<string> &List) +{ + + for (unsigned int I = 0; I < List.size(); I++) + { + const char *Str = List[I].c_str(); + const char *Start, *End; + if ((Start = strstr(Str,"/binary-")) == 0) + continue; + + // Between Start and End is the architecture + Start += 8; + if ((End = strstr(Start,"/")) != 0 && Start != End && + APT::Configuration::checkArchitecture(string(Start, End)) == true) + continue; // okay, architecture is accepted + + // not accepted -> Erase it + List.erase(List.begin() + I); + --I; // the next entry is at the same index after the erase + } + + return true; +} + /*}}}*/ +// DropTranslation - Dump unwanted Translation-<lang> files /*{{{*/ +// --------------------------------------------------------------------- +/* Here we drop everything that is not configured in Acquire::Languages */ +bool pkgCdrom::DropTranslation(vector<string> &List) +{ + for (unsigned int I = 0; I < List.size(); I++) + { + const char *Start; + if ((Start = strstr(List[I].c_str(), "/Translation-")) == NULL) + continue; + Start += strlen("/Translation-"); + + if (APT::Configuration::checkLanguage(Start, true) == true) + continue; + + // not accepted -> Erase it + List.erase(List.begin() + I); + --I; // the next entry is at the same index after the erase + } + + return true; +} + /*}}}*/ +// DropRepeats - Drop repeated files resulting from symlinks /*{{{*/ +// --------------------------------------------------------------------- +/* Here we go and stat every file that we found and strip dup inodes. */ +bool pkgCdrom::DropRepeats(vector<string> &List,const char *Name) +{ + bool couldFindAllFiles = true; + // Get a list of all the inodes + ino_t *Inodes = new ino_t[List.size()]; + for (unsigned int I = 0; I != List.size(); ++I) + { + struct stat Buf; + bool found = false; + + std::vector<APT::Configuration::Compressor> const compressor = APT::Configuration::getCompressors(); + for (std::vector<APT::Configuration::Compressor>::const_iterator c = compressor.begin(); + c != compressor.end(); ++c) + { + std::string const filename = List[I] + Name + c->Extension; + if (stat(filename.c_str(), &Buf) != 0) + continue; + Inodes[I] = Buf.st_ino; + found = true; + break; + } + + if (found == false) + { + _error->Errno("stat","Failed to stat %s%s",List[I].c_str(), Name); + couldFindAllFiles = false; + Inodes[I] = 0; + } + } + + // Look for dups + for (unsigned int I = 0; I != List.size(); I++) + { + if (Inodes[I] == 0) + continue; + for (unsigned int J = I+1; J < List.size(); J++) + { + // No match + if (Inodes[J] == 0 || Inodes[J] != Inodes[I]) + continue; + + // We score the two paths.. and erase one + int ScoreA = Score(List[I]); + int ScoreB = Score(List[J]); + if (ScoreA < ScoreB) + { + List[I] = string(); + break; + } + + List[J] = string(); + } + } + delete[] Inodes; + + // Wipe erased entries + for (unsigned int I = 0; I < List.size();) + { + if (List[I].empty() == false) + I++; + else + List.erase(List.begin()+I); + } + + return couldFindAllFiles; +} + /*}}}*/ +// ReduceSourceList - Takes the path list and reduces it /*{{{*/ +// --------------------------------------------------------------------- +/* This takes the list of source list expressed entries and collects + similar ones to form a single entry for each dist */ +void pkgCdrom::ReduceSourcelist(string /*CD*/,vector<string> &List) +{ + sort(List.begin(),List.end()); + + // Collect similar entries + for (vector<string>::iterator I = List.begin(); I != List.end(); ++I) + { + // Find a space.. + string::size_type Space = (*I).find(' '); + if (Space == string::npos) + continue; + string::size_type SSpace = (*I).find(' ',Space + 1); + if (SSpace == string::npos) + continue; + + string Word1 = string(*I,Space,SSpace-Space); + string Prefix = string(*I,0,Space); + string Component = string(*I,SSpace); + for (vector<string>::iterator J = List.begin(); J != I; ++J) + { + // Find a space.. + string::size_type Space2 = (*J).find(' '); + if (Space2 == string::npos) + continue; + string::size_type SSpace2 = (*J).find(' ',Space2 + 1); + if (SSpace2 == string::npos) + continue; + + if (string(*J,0,Space2) != Prefix) + continue; + if (string(*J,Space2,SSpace2-Space2) != Word1) + continue; + + string Component2 = string(*J, SSpace2) + " "; + if (Component2.find(Component + " ") == std::string::npos) + *J += Component; + I->clear(); + } + } + + // Wipe erased entries + for (unsigned int I = 0; I < List.size();) + { + if (List[I].empty() == false) + I++; + else + List.erase(List.begin()+I); + } +} + /*}}}*/ +// WriteDatabase - Write the CDROM Database file /*{{{*/ +// --------------------------------------------------------------------- +/* We rewrite the configuration class associated with the cdrom database. */ +bool pkgCdrom::WriteDatabase(Configuration &Cnf) +{ + string DFile = _config->FindFile("Dir::State::cdroms"); + string NewFile = DFile + ".new"; + + RemoveFile("WriteDatabase", NewFile); + ofstream Out(NewFile.c_str()); + if (!Out) + return _error->Errno("ofstream::ofstream", + "Failed to open %s.new",DFile.c_str()); + + /* Write out all of the configuration directives by walking the + configuration tree */ + Cnf.Dump(Out, NULL, "%F \"%v\";\n", false); + + Out.close(); + + if (FileExists(DFile) == true) + rename(DFile.c_str(), (DFile + '~').c_str()); + if (rename(NewFile.c_str(),DFile.c_str()) != 0) + return _error->Errno("rename","Failed to rename %s.new to %s", + DFile.c_str(),DFile.c_str()); + + return true; +} + /*}}}*/ +// WriteSourceList - Write an updated sourcelist /*{{{*/ +// --------------------------------------------------------------------- +/* This reads the old source list and copies it into the new one. It + appends the new CDROM entries just after the first block of comments. + This places them first in the file. It also removes any old entries + that were the same. */ +bool pkgCdrom::WriteSourceList(string Name,vector<string> &List,bool Source) +{ + if (List.empty() == true) + return true; + + string File = _config->FindFile("Dir::Etc::sourcelist"); + + // Open the stream for reading + ifstream F((FileExists(File)?File.c_str():"/dev/null"), + ios::in ); + if (F.fail() == true) + return _error->Errno("ifstream::ifstream","Opening %s",File.c_str()); + + string NewFile = File + ".new"; + RemoveFile("WriteDatabase", NewFile); + ofstream Out(NewFile.c_str()); + if (!Out) + return _error->Errno("ofstream::ofstream", + "Failed to open %s.new",File.c_str()); + + // Create a short uri without the path + string ShortURI = "cdrom:[" + Name + "]/"; + string ShortURI2 = "cdrom:" + Name + "/"; // For Compatibility + + string Type; + if (Source == true) + Type = "deb-src"; + else + Type = "deb"; + + char Buffer[300]; + int CurLine = 0; + bool First = true; + while (F.eof() == false) + { + F.getline(Buffer,sizeof(Buffer)); + CurLine++; + if (F.fail() && !F.eof()) + return _error->Error(_("Line %u too long in source list %s."), + CurLine,File.c_str()); + _strtabexpand(Buffer,sizeof(Buffer)); + _strstrip(Buffer); + + // Comment or blank + if (Buffer[0] == '#' || Buffer[0] == 0) + { + Out << Buffer << endl; + continue; + } + + if (First == true) + { + for (vector<string>::iterator I = List.begin(); I != List.end(); ++I) + { + string::size_type Space = (*I).find(' '); + if (Space == string::npos) + return _error->Error("Internal error"); + Out << Type << " cdrom:[" << Name << "]/" << string(*I,0,Space) << + " " << string(*I,Space+1) << endl; + } + } + First = false; + + // Grok it + string cType; + string URI; + const char *C = Buffer; + if (ParseQuoteWord(C,cType) == false || + ParseQuoteWord(C,URI) == false) + { + Out << Buffer << endl; + continue; + } + + // Emit lines like this one + if (cType != Type || (string(URI,0,ShortURI.length()) != ShortURI && + string(URI,0,ShortURI.length()) != ShortURI2)) + { + Out << Buffer << endl; + continue; + } + } + + // Just in case the file was empty + if (First == true) + { + for (vector<string>::iterator I = List.begin(); I != List.end(); ++I) + { + string::size_type Space = (*I).find(' '); + if (Space == string::npos) + return _error->Error("Internal error"); + + Out << "deb cdrom:[" << Name << "]/" << string(*I,0,Space) << + " " << string(*I,Space+1) << endl; + } + } + + Out.close(); + + rename(File.c_str(), (File + '~').c_str()); + if (rename(NewFile.c_str(),File.c_str()) != 0) + return _error->Errno("rename","Failed to rename %s.new to %s", + File.c_str(),File.c_str()); + + return true; +} + /*}}}*/ +bool pkgCdrom::UnmountCDROM(std::string const &CDROM, pkgCdromStatus * const log)/*{{{*/ +{ + if (_config->FindB("APT::CDROM::NoMount",false) == true) + return true; + if (log != NULL) + log->Update(_("Unmounting CD-ROM...\n"), STEP_LAST); + return UnmountCdrom(CDROM); +} + /*}}}*/ +bool pkgCdrom::MountAndIdentCDROM(Configuration &Database, std::string &CDROM, std::string &ident, pkgCdromStatus * const log, bool const interactive)/*{{{*/ +{ + // Startup + CDROM = _config->FindDir("Acquire::cdrom::mount"); + if (CDROM[0] == '.') + CDROM= SafeGetCWD() + '/' + CDROM; + + if (log != NULL) + { + string msg; + log->SetTotal(STEP_LAST); + strprintf(msg, _("Using CD-ROM mount point %s\n"), CDROM.c_str()); + log->Update(msg, STEP_PREPARE); + } + + // Unmount the CD and get the user to put in the one they want + if (_config->FindB("APT::CDROM::NoMount", false) == false) + { + if (interactive == true) + { + UnmountCDROM(CDROM, log); + + if(log != NULL) + { + log->Update(_("Waiting for disc...\n"), STEP_WAIT); + if(!log->ChangeCdrom()) { + // user aborted + return false; + } + } + } + + // Mount the new CDROM + if(log != NULL) + log->Update(_("Mounting CD-ROM...\n"), STEP_MOUNT); + + if (MountCdrom(CDROM) == false) + return _error->Error("Failed to mount the cdrom."); + } + + if (IsMounted(CDROM) == false) + return _error->Error("Failed to mount the cdrom."); + + // Hash the CD to get an ID + if (log != NULL) + log->Update(_("Identifying... "), STEP_IDENT); + + if (IdentCdrom(CDROM,ident) == false) + { + ident = ""; + if (log != NULL) + log->Update("\n"); + UnmountCDROM(CDROM, NULL); + return false; + } + + if (log != NULL) + { + string msg; + strprintf(msg, "[%s]\n", ident.c_str()); + log->Update(msg); + } + + // Read the database + string DFile = _config->FindFile("Dir::State::cdroms"); + if (FileExists(DFile) == true) + { + if (ReadConfigFile(Database,DFile) == false) + { + UnmountCDROM(CDROM, NULL); + return _error->Error("Unable to read the cdrom database %s", + DFile.c_str()); + } + } + return true; +} + /*}}}*/ +bool pkgCdrom::Ident(string &ident, pkgCdromStatus *log) /*{{{*/ +{ + Configuration Database; + std::string CDROM; + if (MountAndIdentCDROM(Database, CDROM, ident, log, false) == false) + return false; + + if (log != NULL) + { + string msg; + strprintf(msg, _("Stored label: %s\n"), + Database.Find("CD::"+ident).c_str()); + log->Update(msg); + } + + // Unmount and finish + UnmountCDROM(CDROM, log); + return true; +} + /*}}}*/ +bool pkgCdrom::Add(pkgCdromStatus *log) /*{{{*/ +{ + Configuration Database; + std::string ID, CDROM; + if (MountAndIdentCDROM(Database, CDROM, ID, log, true) == false) + return false; + + if(log != NULL) + log->Update(_("Scanning disc for index files...\n"),STEP_SCAN); + + // Get the CD structure + vector<string> List; + vector<string> SourceList; + vector<string> SigList; + vector<string> TransList; + string StartDir = SafeGetCWD(); + string InfoDir; + if (FindPackages(CDROM,List,SourceList, SigList,TransList,InfoDir,log) == false) + { + if (log != NULL) + log->Update("\n"); + UnmountCDROM(CDROM, NULL); + return false; + } + + if (chdir(StartDir.c_str()) != 0) + { + UnmountCDROM(CDROM, NULL); + return _error->Errno("chdir","Unable to change to %s", StartDir.c_str()); + } + + if (_config->FindB("Debug::aptcdrom",false) == true) + { + cout << "I found (binary):" << endl; + for (vector<string>::iterator I = List.begin(); I != List.end(); ++I) + cout << *I << endl; + cout << "I found (source):" << endl; + for (vector<string>::iterator I = SourceList.begin(); I != SourceList.end(); ++I) + cout << *I << endl; + cout << "I found (Signatures):" << endl; + for (vector<string>::iterator I = SigList.begin(); I != SigList.end(); ++I) + cout << *I << endl; + } + + //log->Update(_("Cleaning package lists..."), STEP_CLEAN); + + // Fix up the list + DropBinaryArch(List); + DropRepeats(List,"Packages"); + DropRepeats(SourceList,"Sources"); + // FIXME: We ignore stat() errors here as we usually have only one of those in use + // This has little potential to drop 'valid' stat() errors as we know that one of these + // files need to exist, but it would be better if we would check it here + _error->PushToStack(); + DropRepeats(SigList,"Release.gpg"); + DropRepeats(SigList,"InRelease"); + _error->RevertToStack(); + DropRepeats(TransList,""); + if (_config->FindB("APT::CDROM::DropTranslation", true) == true) + DropTranslation(TransList); + if(log != NULL) { + string msg; + strprintf(msg, _("Found %zu package indexes, %zu source indexes, " + "%zu translation indexes and %zu signatures\n"), + List.size(), SourceList.size(), TransList.size(), + SigList.size()); + log->Update(msg, STEP_SCAN); + } + + if (List.empty() == true && SourceList.empty() == true) + { + UnmountCDROM(CDROM, NULL); + return _error->Error(_("Unable to locate any package files, perhaps this is not a Debian Disc or the wrong architecture?")); + } + + // Check if the CD is in the database + string Name; + if (Database.Exists("CD::" + ID) == false || + _config->FindB("APT::CDROM::Rename",false) == true) + { + // Try to use the CDs label if at all possible + if (InfoDir.empty() == false && + FileExists(InfoDir + "/info") == true) + { + ifstream F((InfoDir + "/info").c_str()); + if (F.good() == true) + getline(F,Name); + + if (Name.empty() == false) + { + // Escape special characters + string::iterator J = Name.begin(); + for (; J != Name.end(); ++J) + if (*J == '"' || *J == ']' || *J == '[') + *J = '_'; + + if(log != NULL) + { + string msg; + strprintf(msg, _("Found label '%s'\n"), Name.c_str()); + log->Update(msg); + } + Database.Set("CD::" + ID + "::Label",Name); + } + } + + if (_config->FindB("APT::CDROM::Rename",false) == true || + Name.empty() == true) + { + if(log == NULL) + { + UnmountCDROM(CDROM, NULL); + return _error->Error("No disc name found and no way to ask for it"); + } + + while(true) { + if(!log->AskCdromName(Name)) { + // user canceld + UnmountCDROM(CDROM, NULL); + return false; + } + cout << "Name: '" << Name << "'" << endl; + + if (Name.empty() == false && + Name.find('"') == string::npos && + Name.find('[') == string::npos && + Name.find(']') == string::npos) + break; + log->Update(_("That is not a valid name, try again.\n")); + } + } + } + else + Name = Database.Find("CD::" + ID); + + // Escape special characters + string::iterator J = Name.begin(); + for (; J != Name.end(); ++J) + if (*J == '"' || *J == ']' || *J == '[') + *J = '_'; + + Database.Set("CD::" + ID,Name); + if(log != NULL) + { + string msg; + strprintf(msg, _("This disc is called: \n'%s'\n"), Name.c_str()); + log->Update(msg); + log->Update(_("Copying package lists..."), STEP_COPY); + } + + // check for existence and possibly create state directory for copying + string const listDir = _config->FindDir("Dir::State::lists"); + string const partialListDir = listDir + "partial/"; + mode_t const mode = umask(S_IWGRP | S_IWOTH); + bool const creation_fail = (CreateAPTDirectoryIfNeeded(_config->FindDir("Dir::State"), partialListDir) == false && + CreateAPTDirectoryIfNeeded(listDir, partialListDir) == false); + umask(mode); + if (creation_fail == true) + { + UnmountCDROM(CDROM, NULL); + return _error->Errno("cdrom", _("List directory %s is missing."), (listDir + "partial").c_str()); + } + + // take care of the signatures and copy them if they are ok + // (we do this before PackageCopy as it modifies "List" and "SourceList") + SigVerify SignVerify; + SignVerify.CopyAndVerify(CDROM, Name, SigList, List, SourceList); + + // Copy the package files to the state directory + PackageCopy Copy; + SourceCopy SrcCopy; + TranslationsCopy TransCopy; + if (Copy.CopyPackages(CDROM,Name,List, log) == false || + SrcCopy.CopyPackages(CDROM,Name,SourceList, log) == false || + TransCopy.CopyTranslations(CDROM,Name,TransList, log) == false) + { + UnmountCDROM(CDROM, NULL); + return false; + } + + // reduce the List so that it takes less space in sources.list + ReduceSourcelist(CDROM,List); + ReduceSourcelist(CDROM,SourceList); + + // Write the database and sourcelist + if (_config->FindB("APT::cdrom::NoAct",false) == false) + { + if (WriteDatabase(Database) == false) + { + UnmountCDROM(CDROM, NULL); + return false; + } + + if(log != NULL) + log->Update(_("Writing new source list\n"), STEP_WRITE); + if (WriteSourceList(Name,List,false) == false || + WriteSourceList(Name,SourceList,true) == false) + { + UnmountCDROM(CDROM, NULL); + return false; + } + } + + // Print the sourcelist entries + if(log != NULL) + log->Update(_("Source list entries for this disc are:\n")); + + for (vector<string>::iterator I = List.begin(); I != List.end(); ++I) + { + string::size_type Space = (*I).find(' '); + if (Space == string::npos) + { + UnmountCDROM(CDROM, NULL); + return _error->Error("Internal error"); + } + + if(log != NULL) + { + stringstream msg; + msg << "deb cdrom:[" << Name << "]/" << string(*I,0,Space) << + " " << string(*I,Space+1) << endl; + log->Update(msg.str()); + } + } + + for (vector<string>::iterator I = SourceList.begin(); I != SourceList.end(); ++I) + { + string::size_type Space = (*I).find(' '); + if (Space == string::npos) + { + UnmountCDROM(CDROM, NULL); + return _error->Error("Internal error"); + } + + if(log != NULL) { + stringstream msg; + msg << "deb-src cdrom:[" << Name << "]/" << string(*I,0,Space) << + " " << string(*I,Space+1) << endl; + log->Update(msg.str()); + } + } + + // Unmount and finish + UnmountCDROM(CDROM, log); + return true; +} + /*}}}*/ + +pkgUdevCdromDevices::pkgUdevCdromDevices() /*{{{*/ + : d(NULL) +{ +} + /*}}}*/ + +bool pkgUdevCdromDevices::Dlopen() /*{{{*/ +{ + return true; +} + /*}}}*/ +// convenience interface, this will just call ScanForRemovable /*{{{*/ +vector<CdromDevice> pkgUdevCdromDevices::Scan() +{ + bool CdromOnly = _config->FindB("APT::cdrom::CdromOnly", true); + return ScanForRemovable(CdromOnly); +} + /*}}}*/ +vector<CdromDevice> pkgUdevCdromDevices::ScanForRemovable(bool CdromOnly)/*{{{*/ +{ + vector<CdromDevice> cdrom_devices; +#ifdef HAVE_UDEV + struct udev_enumerate *enumerate; + struct udev_list_entry *l, *devices; + struct udev *udev_ctx; + + udev_ctx = udev_new(); + enumerate = udev_enumerate_new (udev_ctx); + if (CdromOnly) + udev_enumerate_add_match_property(enumerate, "ID_CDROM", "1"); + else { + udev_enumerate_add_match_sysattr(enumerate, "removable", "1"); + } + + udev_enumerate_scan_devices (enumerate); + devices = udev_enumerate_get_list_entry (enumerate); + for (l = devices; l != NULL; l = udev_list_entry_get_next (l)) + { + CdromDevice cdrom; + struct udev_device *udevice; + udevice = udev_device_new_from_syspath (udev_enumerate_get_udev (enumerate), udev_list_entry_get_name (l)); + if (udevice == NULL) + continue; + const char* devnode = udev_device_get_devnode(udevice); + + // try fstab_dir first + string mountpath; + const char* mp = udev_device_get_property_value(udevice, "FSTAB_DIR"); + if (mp) + mountpath = string(mp); + else + mountpath = FindMountPointForDevice(devnode); + + // fill in the struct + cdrom.DeviceName = string(devnode); + if (mountpath != "") { + cdrom.MountPath = mountpath; + string s = mountpath; + cdrom.Mounted = IsMounted(s); + } else { + cdrom.Mounted = false; + cdrom.MountPath = ""; + } + cdrom_devices.push_back(cdrom); + } +#endif + return cdrom_devices; +} + /*}}}*/ + +pkgUdevCdromDevices::~pkgUdevCdromDevices() /*{{{*/ +{ +} + /*}}}*/ + +pkgCdromStatus::pkgCdromStatus() : d(NULL), totalSteps(0) {} +pkgCdromStatus::~pkgCdromStatus() {} + +pkgCdrom::pkgCdrom() : d(NULL) {} +pkgCdrom::~pkgCdrom() {} diff --git a/apt-pkg/cdrom.h b/apt-pkg/cdrom.h new file mode 100644 index 0000000..6bc2b34 --- /dev/null +++ b/apt-pkg/cdrom.h @@ -0,0 +1,135 @@ +#ifndef PKGLIB_CDROM_H +#define PKGLIB_CDROM_H + +#include <apt-pkg/macros.h> + +#include <string> +#include <vector> + +#include <stddef.h> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/init.h> +using namespace std; +#endif + +class Configuration; +class OpProgress; + +class pkgCdromStatus /*{{{*/ +{ + void * const d; + protected: + int totalSteps; + + public: + pkgCdromStatus(); + virtual ~pkgCdromStatus(); + + // total steps + virtual void SetTotal(int total) { totalSteps = total; }; + // update steps, will be called regularly as a "pulse" + virtual void Update(std::string text="", int current=0) = 0; + + // ask for cdrom insert + virtual bool ChangeCdrom() = 0; + // ask for cdrom name + virtual bool AskCdromName(std::string &Name) = 0; + // Progress indicator for the Index rewriter + virtual OpProgress* GetOpProgress() {return NULL; }; +}; + /*}}}*/ +class pkgCdrom /*{{{*/ +{ + protected: + enum { + STEP_PREPARE = 1, + STEP_UNMOUNT, + STEP_WAIT, + STEP_MOUNT, + STEP_IDENT, + STEP_SCAN, + STEP_COPY, + STEP_WRITE, + STEP_UNMOUNT3, + STEP_LAST + }; + + + bool FindPackages(std::string CD, + std::vector<std::string> &List, + std::vector<std::string> &SList, + std::vector<std::string> &SigList, + std::vector<std::string> &TransList, + std::string &InfoDir, pkgCdromStatus *log, + unsigned int Depth = 0); + bool DropBinaryArch(std::vector<std::string> &List); + bool DropRepeats(std::vector<std::string> &List,const char *Name); + bool DropTranslation(std::vector<std::string> &List); + void ReduceSourcelist(std::string CD,std::vector<std::string> &List); + bool WriteDatabase(Configuration &Cnf); + bool WriteSourceList(std::string Name,std::vector<std::string> &List,bool Source); + int Score(std::string Path); + + public: + bool Ident(std::string &ident, pkgCdromStatus *log); + bool Add(pkgCdromStatus *log); + + pkgCdrom(); + virtual ~pkgCdrom(); + + private: + void * const d; + + APT_HIDDEN bool MountAndIdentCDROM(Configuration &Database, std::string &CDROM, + std::string &ident, pkgCdromStatus * const log, bool const interactive); + APT_HIDDEN bool UnmountCDROM(std::string const &CDROM, pkgCdromStatus * const log); +}; + /*}}}*/ + + +// class that uses libudev to find cdrom/removable devices dynamically +struct CdromDevice /*{{{*/ +{ + std::string DeviceName; + bool Mounted; + std::string MountPath; +}; + /*}}}*/ +class pkgUdevCdromDevices /*{{{*/ +{ + void * const d; + + protected: + // old libudev dlopen structure, not used anymore, but can't break ABI + void *_padding; + struct _padding *(*_padding__new)(void); + int (*_padding__enumerate_add_match_property)(struct _padding_enumerate *_padding_enumerate, const char *property, const char *value); + int (*_padding__enumerate_scan_devices)(struct _padding_enumerate *_padding_enumerate); + struct _padding_list_entry *(*_padding__enumerate_get_list_entry)(struct _padding_enumerate *_padding_enumerate); + struct _padding_device *(*_padding__device_new_from_syspath)(struct _padding *_padding, const char *syspath); + struct _padding *(*_padding__enumerate_get__padding)(struct _padding_enumerate *_padding_enumerate); + const char *(*_padding__list_entry_get_name)(struct _padding_list_entry *list_entry); + const char *(*_padding__device_get_devnode)(struct _padding_device *_padding_device); + struct _padding_enumerate *(*_padding__enumerate_new)(struct _padding *_padding); + struct _padding_list_entry *(*_padding__list_entry_get_next)(struct _padding_list_entry *list_entry); + const char *(*_padding__device_get_property_value)(struct _padding_device *_padding_device, const char *key); + int (*_padding__enumerate_add_match_sysattr)(struct _padding_enumerate *_padding_enumerate, const char *property, const char *value); + // end lib_padding dlopen + + public: + pkgUdevCdromDevices(); + virtual ~pkgUdevCdromDevices(); + + // try to open + bool Dlopen(); + + // convenience interface, this will just call ScanForRemovable + // with "APT::cdrom::CdromOnly" + std::vector<CdromDevice> Scan(); + + std::vector<CdromDevice> ScanForRemovable(bool CdromOnly); +}; + /*}}}*/ + +#endif diff --git a/apt-pkg/clean.cc b/apt-pkg/clean.cc new file mode 100644 index 0000000..f335554 --- /dev/null +++ b/apt-pkg/clean.cc @@ -0,0 +1,151 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Clean - Clean out downloaded directories + + ##################################################################### */ + /*}}}*/ +// Includes /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/clean.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/strutl.h> + +#include <string> +#include <dirent.h> +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ +// ArchiveCleaner::Go - Perform smart cleanup of the archive /*{{{*/ +// --------------------------------------------------------------------- +/* Scan the directory for files to erase, we check the version information + against our database to see if it is interesting */ +bool pkgArchiveCleaner::Go(std::string Dir,pkgCache &Cache) +{ + bool CleanInstalled = _config->FindB("APT::Clean-Installed",true); + + if(Dir == "/") + return _error->Error(_("Clean of %s is not supported"), Dir.c_str()); + + // non-existing directories are always clean + // we do not check for a directory explicitly to support symlinks + if (FileExists(Dir) == false) + return true; + + auto const withoutChangingDir = dynamic_cast<pkgArchiveCleaner2*>(this); + int const dirfd = open(Dir.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (dirfd == -1) + return _error->Errno("open",_("Unable to read %s"),Dir.c_str()); + std::string CWD; + if (withoutChangingDir == nullptr) + { + CWD = SafeGetCWD(); + if (fchdir(dirfd) != 0) + return _error->Errno("fchdir",_("Unable to change to %s"),Dir.c_str()); + } + DIR * const D = fdopendir(dirfd); + if (D == nullptr) + return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str()); + + for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D)) + { + // Skip some files.. + if (strcmp(Dir->d_name, "lock") == 0 || + strcmp(Dir->d_name, "partial") == 0 || + strcmp(Dir->d_name, "auxfiles") == 0 || + strcmp(Dir->d_name, "lost+found") == 0 || + strcmp(Dir->d_name, ".") == 0 || + strcmp(Dir->d_name, "..") == 0) + continue; + + struct stat St; + if (fstatat(dirfd, Dir->d_name,&St, 0) != 0) + { + _error->Errno("stat",_("Unable to stat %s."),Dir->d_name); + closedir(D); + return false; + } + + // Grab the package name + const char *I = Dir->d_name; + for (; *I != 0 && *I != '_';I++); + if (*I != '_') + continue; + std::string Pkg = DeQuoteString(std::string(Dir->d_name,I-Dir->d_name)); + + // Grab the version + const char *Start = I + 1; + for (I = Start; *I != 0 && *I != '_';I++); + if (*I != '_') + continue; + std::string Ver = DeQuoteString(std::string(Start,I-Start)); + + // Grab the arch + Start = I + 1; + for (I = Start; *I != 0 && *I != '.' ;I++); + if (*I != '.') + continue; + std::string const Arch = DeQuoteString(std::string(Start,I-Start)); + + // ignore packages of unconfigured architectures + if (APT::Configuration::checkArchitecture(Arch) == false) + continue; + + // Lookup the package + pkgCache::PkgIterator P = Cache.FindPkg(Pkg, Arch); + if (P.end() != true) + { + pkgCache::VerIterator V = P.VersionList(); + for (; V.end() == false; ++V) + { + // See if we can fetch this version at all + bool IsFetchable = false; + for (pkgCache::VerFileIterator J = V.FileList(); + J.end() == false; ++J) + { + if (CleanInstalled == true && + J.File().Flagged(pkgCache::Flag::NotSource)) + continue; + IsFetchable = true; + break; + } + + // See if this version matches the file + if (IsFetchable == true && Ver == V.VerStr()) + break; + } + + // We found a match, keep the file + if (V.end() == false) + continue; + } + + if (withoutChangingDir == nullptr) + { + APT_IGNORE_DEPRECATED_PUSH + Erase(Dir->d_name, Pkg, Ver, St); + APT_IGNORE_DEPRECATED_POP + } + else + withoutChangingDir->Erase(dirfd, Dir->d_name, Pkg, Ver, St); + } + closedir(D); + if (withoutChangingDir == nullptr && chdir(CWD.c_str()) != 0) + return _error->Errno("chdir", _("Unable to change to %s"),Dir.c_str()); + return true; +} + /*}}}*/ + +pkgArchiveCleaner::pkgArchiveCleaner() : d(NULL) {} +pkgArchiveCleaner::~pkgArchiveCleaner() {} diff --git a/apt-pkg/clean.h b/apt-pkg/clean.h new file mode 100644 index 0000000..d6490a0 --- /dev/null +++ b/apt-pkg/clean.h @@ -0,0 +1,49 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Clean - Clean out downloaded directories + + ##################################################################### */ + /*}}}*/ +#ifndef APTPKG_CLEAN_H +#define APTPKG_CLEAN_H + +#ifndef APT_10_CLEANER_HEADERS +#include <apt-pkg/pkgcache.h> +#endif + +#include <string> + +#include <apt-pkg/macros.h> + +class pkgCache; + +class pkgArchiveCleaner +{ + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + protected: + + APT_DEPRECATED_MSG("Use pkgArchiveCleaner2 to avoid CWD expectations and chdir") virtual void Erase(const char * /*File*/,std::string /*Pkg*/,std::string /*Ver*/,struct stat & /*St*/) {}; + + public: + + bool Go(std::string Dir,pkgCache &Cache); + + pkgArchiveCleaner(); + virtual ~pkgArchiveCleaner(); +}; +// TODO: merge classes and "erase" the old way +class pkgArchiveCleaner2: public pkgArchiveCleaner +{ + friend class pkgArchiveCleaner; +protected: + using pkgArchiveCleaner::Erase; + virtual void Erase(int const dirfd, char const * const File, + std::string const &Pkg,std::string const &Ver, + struct stat const &St) = 0; +}; + +#endif diff --git a/apt-pkg/contrib/cdromutl.cc b/apt-pkg/contrib/cdromutl.cc new file mode 100644 index 0000000..9db3980 --- /dev/null +++ b/apt-pkg/contrib/cdromutl.cc @@ -0,0 +1,293 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + CDROM Utilities - Some functions to manipulate CDROM mounts. + + These are here for the cdrom method and apt-cdrom. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/cdromutl.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/md5.h> +#include <apt-pkg/strutl.h> + +#include <iostream> +#include <string> +#include <vector> +#include <dirent.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ + +using std::string; + +// IsMounted - Returns true if the mount point is mounted /*{{{*/ +// --------------------------------------------------------------------- +/* This is a simple algorithm that should always work, we stat the mount point + and the '..' file in the mount point and see if they are on the same device. + By definition if they are the same then it is not mounted. This should + account for symlinked mount points as well. */ +bool IsMounted(string &Path) +{ + if (Path.empty() == true) + return false; + + // Need that trailing slash for directories + if (Path[Path.length() - 1] != '/') + Path += '/'; + + // if the path has a ".disk" directory we treat it as mounted + // this way even extracted copies of disks are recognized + if (DirectoryExists(Path + ".disk/") == true) + return true; + + /* First we check if the path is actually mounted, we do this by + stating the path and the previous directory (careful of links!) + and comparing their device fields. */ + struct stat Buf,Buf2; + if (stat(Path.c_str(),&Buf) != 0 || + stat((Path + "../").c_str(),&Buf2) != 0) + return _error->Errno("stat",_("Unable to stat the mount point %s"),Path.c_str()); + + if (Buf.st_dev == Buf2.st_dev) + return false; + return true; +} + /*}}}*/ +// UnmountCdrom - Unmount a cdrom /*{{{*/ +// --------------------------------------------------------------------- +/* Forking umount works much better than the umount syscall which can + leave /etc/mtab inconsitant. We drop all messages this produces. */ +bool UnmountCdrom(string Path) +{ + // do not generate errors, even if the mountpoint does not exist + // the mountpoint might be auto-created by the mount command + // and a non-existing mountpoint is surely not mounted + _error->PushToStack(); + bool const mounted = IsMounted(Path); + _error->RevertToStack(); + if (mounted == false) + return true; + + for (int i=0;i<3;i++) + { + + int Child = ExecFork(); + + // The child + if (Child == 0) + { + // Make all the fds /dev/null + int const null_fd = open("/dev/null",O_RDWR); + for (int I = 0; I != 3; ++I) + dup2(null_fd, I); + + if (_config->Exists("Acquire::cdrom::"+Path+"::UMount") == true) + { + if (system(_config->Find("Acquire::cdrom::"+Path+"::UMount").c_str()) != 0) + _exit(100); + _exit(0); + } + else + { + const char * const Args[] = { + "umount", + Path.c_str(), + nullptr + }; + execvp(Args[0], const_cast<char **>(Args)); + _exit(100); + } + } + + // if it can not be umounted, give it a bit more time + // this can happen when auto-mount magic or fs/cdrom prober attack + if (ExecWait(Child,"umount",true) == true) + return true; + sleep(1); + } + + return false; +} + /*}}}*/ +// MountCdrom - Mount a cdrom /*{{{*/ +// --------------------------------------------------------------------- +/* We fork mount and drop all messages */ +bool MountCdrom(string Path, string DeviceName) +{ + // do not generate errors, even if the mountpoint does not exist + // the mountpoint might be auto-created by the mount command + _error->PushToStack(); + bool const mounted = IsMounted(Path); + _error->RevertToStack(); + if (mounted == true) + return true; + + int Child = ExecFork(); + + // The child + if (Child == 0) + { + // Make all the fds /dev/null + int const null_fd = open("/dev/null",O_RDWR); + for (int I = 0; I != 3; ++I) + dup2(null_fd, I); + + if (_config->Exists("Acquire::cdrom::"+Path+"::Mount") == true) + { + if (system(_config->Find("Acquire::cdrom::"+Path+"::Mount").c_str()) != 0) + _exit(100); + _exit(0); + } + else + { + const char *Args[10]; + Args[0] = "mount"; + if (DeviceName == "") + { + Args[1] = Path.c_str(); + Args[2] = 0; + } else { + Args[1] = DeviceName.c_str(); + Args[2] = Path.c_str(); + Args[3] = 0; + } + execvp(Args[0],(char **)Args); + _exit(100); + } + } + + // Wait for mount + return ExecWait(Child,"mount",true); +} + /*}}}*/ +// IdentCdrom - Generate a unique string for this CD /*{{{*/ +// --------------------------------------------------------------------- +/* We convert everything we hash into a string, this prevents byte size/order + from effecting the outcome. */ +bool IdentCdrom(string CD,string &Res,unsigned int Version) +{ + MD5Summation Hash; + bool writable_media = false; + + int dirfd = open(CD.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (dirfd == -1) + return _error->Errno("open",_("Unable to read %s"),CD.c_str()); + + // if we are on a writable medium (like a usb-stick) that is just + // used like a cdrom don't use "." as it will constantly change, + // use .disk instead + if (faccessat(dirfd, ".", W_OK, 0) == 0) + { + int diskfd = openat(dirfd, "./.disk", O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0); + if (diskfd != -1) + { + close(dirfd); + dirfd = diskfd; + writable_media = true; + CD = CD.append("/.disk"); + if (_config->FindB("Debug::aptcdrom",false) == true) + std::clog << "Found writable cdrom, using alternative path: " << CD + << std::endl; + } + } + + DIR * const D = fdopendir(dirfd); + if (D == nullptr) + return _error->Errno("opendir",_("Unable to read %s"),CD.c_str()); + + /* Run over the directory, we assume that the reader order will never + change as the media is read-only. In theory if the kernel did + some sort of wacked caching this might not be true.. */ + for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D)) + { + // Skip some files.. + if (strcmp(Dir->d_name,".") == 0 || + strcmp(Dir->d_name,"..") == 0) + continue; + + std::string S; + if (Version <= 1) + S = std::to_string(Dir->d_ino); + else + { + struct stat Buf; + if (fstatat(dirfd, Dir->d_name, &Buf, 0) != 0) + continue; + S = std::to_string(Buf.st_mtime); + } + + Hash.Add(S.c_str()); + Hash.Add(Dir->d_name); + } + + // Some stats from the fsys + std::string S; + if (_config->FindB("Debug::identcdrom",false) == false) + { + struct statvfs Buf; + if (fstatvfs(dirfd, &Buf) != 0) + return _error->Errno("statfs",_("Failed to stat the cdrom")); + + // We use a kilobyte block size to avoid overflow + S = std::to_string(Buf.f_blocks * (Buf.f_bsize / 1024)); + if (writable_media == false) + S.append(" ").append(std::to_string(Buf.f_bfree * (Buf.f_bsize / 1024))); + Hash.Add(S.c_str(), S.length()); + strprintf(S, "-%u", Version); + } + else + strprintf(S, "-%u.debug", Version); + + closedir(D); + Res = Hash.Result().Value().append(std::move(S)); + return true; +} + /*}}}*/ +// FindMountPointForDevice - Find mountpoint for the given device /*{{{*/ +string FindMountPointForDevice(const char *devnode) +{ + // this is the order that mount uses as well + std::vector<std::string> const mounts = _config->FindVector("Dir::state::MountPoints", "/etc/mtab,/proc/mount"); + + for (std::vector<std::string>::const_iterator m = mounts.begin(); m != mounts.end(); ++m) + if (FileExists(*m) == true) + { + char * line = NULL; + size_t line_len = 0; + FILE * f = fopen(m->c_str(), "r"); + while(getline(&line, &line_len, f) != -1) + { + char * out[] = { NULL, NULL, NULL }; + TokSplitString(' ', line, out, 3); + if (out[2] != NULL || out[1] == NULL || out[0] == NULL) + continue; + if (strcmp(out[0], devnode) != 0) + continue; + fclose(f); + // unescape the \0XXX chars in the path + string mount_point = out[1]; + free(line); + return DeEscapeString(mount_point); + } + fclose(f); + free(line); + } + + return string(); +} + /*}}}*/ diff --git a/apt-pkg/contrib/cdromutl.h b/apt-pkg/contrib/cdromutl.h new file mode 100644 index 0000000..4e07e42 --- /dev/null +++ b/apt-pkg/contrib/cdromutl.h @@ -0,0 +1,25 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + CDROM Utilities - Some functions to manipulate CDROM mounts. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_CDROMUTL_H +#define PKGLIB_CDROMUTL_H + +#include <string> + +#ifndef APT_8_CLEANER_HEADERS +using std::string; +#endif + +// mount cdrom, DeviceName (e.g. /dev/sr0) is optional +bool MountCdrom(std::string Path, std::string DeviceName=""); +bool UnmountCdrom(std::string Path); +bool IdentCdrom(std::string CD,std::string &Res,unsigned int Version = 2); +bool IsMounted(std::string &Path); +std::string FindMountPointForDevice(const char *device); + +#endif diff --git a/apt-pkg/contrib/cmndline.cc b/apt-pkg/contrib/cmndline.cc new file mode 100644 index 0000000..b2a96ca --- /dev/null +++ b/apt-pkg/contrib/cmndline.cc @@ -0,0 +1,445 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Command Line Class - Sophisticated command line parser + + This source is placed in the Public Domain, do with it what you will + It was originally written by Jason Gunthorpe <jgg@debian.org>. + + ##################################################################### */ + /*}}}*/ +// Include files /*{{{*/ +#include <config.h> + +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/strutl.h> + +#include <string> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include <apti18n.h> + /*}}}*/ +using namespace std; + +// CommandLine::CommandLine - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +CommandLine::CommandLine(Args *AList,Configuration *Conf) : ArgList(AList), + Conf(Conf), FileList(0) +{ +} +CommandLine::CommandLine() : ArgList(NULL), Conf(NULL), FileList(0) +{ +} + /*}}}*/ +// CommandLine::~CommandLine - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +CommandLine::~CommandLine() +{ + delete [] FileList; +} + /*}}}*/ +// CommandLine::GetCommand - return the first non-option word /*{{{*/ +char const * CommandLine::GetCommand(Dispatch const * const Map, + unsigned int const argc, char const * const * const argv) +{ + // if there is a -- on the line there must be the word we search for either + // before it (as -- marks the end of the options) or right after it (as we can't + // decide if the command is actually an option, given that in theory, you could + // have parameters named like commands) + for (size_t i = 1; i < argc; ++i) + { + if (strcmp(argv[i], "--") != 0) + continue; + // check if command is before -- + for (size_t k = 1; k < i; ++k) + for (size_t j = 0; Map[j].Match != NULL; ++j) + if (strcmp(argv[k], Map[j].Match) == 0) + return Map[j].Match; + // see if the next token after -- is the command + ++i; + if (i < argc) + for (size_t j = 0; Map[j].Match != NULL; ++j) + if (strcmp(argv[i], Map[j].Match) == 0) + return Map[j].Match; + // we found a --, but not a command + return NULL; + } + // no --, so search for the first word matching a command + // FIXME: How like is it that an option parameter will be also a valid Match ? + for (size_t i = 1; i < argc; ++i) + { + if (*(argv[i]) == '-') + continue; + for (size_t j = 0; Map[j].Match != NULL; ++j) + if (strcmp(argv[i], Map[j].Match) == 0) + return Map[j].Match; + } + return NULL; +} + /*}}}*/ +// CommandLine::Parse - Main action member /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool CommandLine::Parse(int argc,const char **argv) +{ + delete [] FileList; + FileList = new const char *[argc]; + const char **Files = FileList; + int I; + for (I = 1; I != argc; I++) + { + const char *Opt = argv[I]; + + // It is not an option + if (*Opt != '-') + { + *Files++ = Opt; + continue; + } + + Opt++; + + // Double dash signifies the end of option processing + if (*Opt == '-' && Opt[1] == 0) + { + I++; + break; + } + + // Single dash is a short option + if (*Opt != '-') + { + // Iterate over each letter + while (*Opt != 0) + { + // Search for the option + Args *A; + for (A = ArgList; A->end() == false && A->ShortOpt != *Opt; A++); + if (A->end() == true) + return _error->Error(_("Command line option '%c' [from %s] is not understood in combination with the other options."),*Opt,argv[I]); + + if (HandleOpt(I,argc,argv,Opt,A) == false) + return false; + if (*Opt != 0) + Opt++; + } + continue; + } + + Opt++; + + // Match up to a = against the list + Args *A; + const char *OptEnd = strchrnul(Opt, '='); + for (A = ArgList; A->end() == false && + (A->LongOpt == 0 || stringcasecmp(Opt,OptEnd,A->LongOpt) != 0); + ++A); + + // Failed, look for a word after the first - (no-foo) + bool PreceedMatch = false; + if (A->end() == true) + { + Opt = (const char*) memchr(Opt, '-', OptEnd - Opt); + if (Opt == NULL) + return _error->Error(_("Command line option %s is not understood in combination with the other options"),argv[I]); + Opt++; + + for (A = ArgList; A->end() == false && + (A->LongOpt == 0 || stringcasecmp(Opt,OptEnd,A->LongOpt) != 0); + ++A); + + // Failed again.. + if (A->end() == true && OptEnd - Opt != 1) + return _error->Error(_("Command line option %s is not understood in combination with the other options"),argv[I]); + + // The option could be a single letter option prefixed by a no-.. + if (A->end() == true) + { + for (A = ArgList; A->end() == false && A->ShortOpt != *Opt; A++); + + if (A->end() == true) + return _error->Error(_("Command line option %s is not understood in combination with the other options"),argv[I]); + } + + // The option is not boolean + if (A->IsBoolean() == false) + return _error->Error(_("Command line option %s is not boolean"),argv[I]); + PreceedMatch = true; + } + + // Deal with it. + OptEnd--; + if (HandleOpt(I,argc,argv,OptEnd,A,PreceedMatch) == false) + return false; + } + + // Copy any remaining file names over + for (; I != argc; I++) + *Files++ = argv[I]; + *Files = 0; + + SaveInConfig(argc, argv); + + return true; +} + /*}}}*/ +// CommandLine::HandleOpt - Handle a single option including all flags /*{{{*/ +// --------------------------------------------------------------------- +/* This is a helper function for parser, it looks at a given argument + and looks for specific patterns in the string, it gets tokanized + -ruffly- like -*[yes|true|enable]-(o|longopt)[=][ ][argument] */ +bool CommandLine::HandleOpt(int &I,int argc,const char *argv[], + const char *&Opt,Args *A,bool PreceedMatch) +{ + const char *Argument = 0; + bool CertainArg = false; + int IncI = 0; + + /* Determine the possible location of an option or 0 if their is + no option */ + if (Opt[1] == 0) + { + if (I + 1 < argc && argv[I+1][0] != '-') + Argument = argv[I+1]; + + IncI = 1; + } + else + { + if (Opt[1] == '=') + { + CertainArg = true; + Argument = Opt + 2; + } + else + Argument = Opt + 1; + } + + // Option is an argument set + if ((A->Flags & HasArg) == HasArg) + { + if (Argument == 0) + return _error->Error(_("Option %s requires an argument."),argv[I]); + Opt += strlen(Opt); + I += IncI; + + // Parse a configuration file + if ((A->Flags & ConfigFile) == ConfigFile) + return ReadConfigFile(*Conf,Argument); + + // Arbitrary item specification + if ((A->Flags & ArbItem) == ArbItem) + { + const char * const J = strchr(Argument, '='); + if (J == nullptr) + return _error->Error(_("Option %s: Configuration item specification must have an =<val>."),argv[I]); + + Conf->Set(string(Argument,J-Argument), J+1); + return true; + } + + const char *I = strchrnul(A->ConfName, ' '); + if (*I == ' ') + Conf->Set(string(A->ConfName,0,I-A->ConfName),string(I+1) + Argument); + else + Conf->Set(A->ConfName,string(I) + Argument); + + return true; + } + + // Option is an integer level + if ((A->Flags & IntLevel) == IntLevel) + { + // There might be an argument + if (Argument != 0) + { + char *EndPtr; + unsigned long Value = strtol(Argument,&EndPtr,10); + + // Conversion failed and the argument was specified with an =s + if (EndPtr == Argument && CertainArg == true) + return _error->Error(_("Option %s requires an integer argument, not '%s'"),argv[I],Argument); + + // Conversion was ok, set the value and return + if (EndPtr != 0 && EndPtr != Argument && *EndPtr == 0) + { + Conf->Set(A->ConfName,Value); + Opt += strlen(Opt); + I += IncI; + return true; + } + } + + // Increase the level + Conf->Set(A->ConfName,Conf->FindI(A->ConfName)+1); + return true; + } + + // Option is a boolean + int Sense = -1; // -1 is unspecified, 0 is yes 1 is no + + // Look for an argument. + while (1) + { + // Look at preceding text + char Buffer[300]; + if (Argument == 0) + { + if (PreceedMatch == false) + break; + + if (strlen(argv[I]) >= sizeof(Buffer)) + return _error->Error(_("Option '%s' is too long"),argv[I]); + + // Skip the leading dash + const char *J = argv[I]; + for (; *J != 0 && *J == '-'; J++); + + const char *JEnd = strchr(J, '-'); + if (JEnd != NULL) + { + strncpy(Buffer,J,JEnd - J); + Buffer[JEnd - J] = 0; + Argument = Buffer; + CertainArg = true; + } + else + break; + } + + // Check for boolean + Sense = StringToBool(Argument); + if (Sense >= 0) + { + // Eat the argument + if (Argument != Buffer) + { + Opt += strlen(Opt); + I += IncI; + } + break; + } + + if (CertainArg == true) + return _error->Error(_("Sense %s is not understood, try true or false."),Argument); + + Argument = 0; + } + + // Indeterminate sense depends on the flag + if (Sense == -1) + { + if ((A->Flags & InvBoolean) == InvBoolean) + Sense = 0; + else + Sense = 1; + } + + Conf->Set(A->ConfName,Sense); + return true; +} + /*}}}*/ +// CommandLine::FileSize - Count the number of filenames /*{{{*/ +// --------------------------------------------------------------------- +/* */ +unsigned int CommandLine::FileSize() const +{ + unsigned int Count = 0; + for (const char **I = FileList; I != 0 && *I != 0; I++) + Count++; + return Count; +} + /*}}}*/ +// CommandLine::DispatchArg - Do something with the first arg /*{{{*/ +bool CommandLine::DispatchArg(Dispatch const * const Map,bool NoMatch) +{ + int I; + for (I = 0; Map[I].Match != 0; I++) + { + if (strcmp(FileList[0],Map[I].Match) == 0) + { + bool Res = Map[I].Handler(*this); + if (Res == false && _error->PendingError() == false) + _error->Error("Handler silently failed"); + return Res; + } + } + + // No matching name + if (Map[I].Match == 0) + { + if (NoMatch == true) + _error->Error(_("Invalid operation %s"),FileList[0]); + } + + return false; +} +bool CommandLine::DispatchArg(Dispatch *Map,bool NoMatch) +{ + Dispatch const * const Map2 = Map; + return DispatchArg(Map2, NoMatch); +} + /*}}}*/ +// CommandLine::SaveInConfig - for output later in a logfile or so /*{{{*/ +// --------------------------------------------------------------------- +/* We save the commandline here to have it around later for e.g. logging. + It feels a bit like a hack here and isn't bulletproof, but it is better + than nothing after all. */ +void CommandLine::SaveInConfig(unsigned int const &argc, char const * const * const argv) +{ + char cmdline[100 + argc * 50]; + memset(cmdline, 0, sizeof(cmdline)); + unsigned int length = 0; + bool lastWasOption = false; + bool closeQuote = false; + for (unsigned int i = 0; i < argc && length < sizeof(cmdline); ++i, ++length) + { + for (unsigned int j = 0; argv[i][j] != '\0' && length < sizeof(cmdline)-2; ++j) + { + // we can't really sensibly deal with quoting so skip it + if (strchr("\"\'\r\n", argv[i][j]) != nullptr) + continue; + cmdline[length++] = argv[i][j]; + if (lastWasOption == true && argv[i][j] == '=') + { + // That is possibly an option: Quote it if it includes spaces, + // the benefit is that this will eliminate also most false positives + const char* c = strchr(&argv[i][j+1], ' '); + if (c == NULL) continue; + cmdline[length++] = '\''; + closeQuote = true; + } + } + if (closeQuote == true) + { + cmdline[length++] = '\''; + closeQuote = false; + } + // Problem: detects also --hello + if (cmdline[length-1] == 'o') + lastWasOption = true; + cmdline[length] = ' '; + } + cmdline[--length] = '\0'; + _config->Set("CommandLine::AsString", cmdline); +} + /*}}}*/ +CommandLine::Args CommandLine::MakeArgs(char ShortOpt, char const *LongOpt, char const *ConfName, unsigned long Flags)/*{{{*/ +{ + /* In theory, this should be a constructor for CommandLine::Args instead, + but this breaks compatibility as gcc thinks this is a c++11 initializer_list */ + CommandLine::Args arg; + arg.ShortOpt = ShortOpt; + arg.LongOpt = LongOpt; + arg.ConfName = ConfName; + arg.Flags = Flags; + return arg; +} + /*}}}*/ diff --git a/apt-pkg/contrib/cmndline.h b/apt-pkg/contrib/cmndline.h new file mode 100644 index 0000000..bdd4f6e --- /dev/null +++ b/apt-pkg/contrib/cmndline.h @@ -0,0 +1,118 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Command Line Class - Sophisticated command line parser + + This class provides a unified command line parser/option handliner/ + configuration mechanism. It allows the caller to specify the option + set and map the option set into the configuration class or other + special functioning. + + Filenames are stripped from the option stream and put into their + own array. + + The argument descriptor array can be initialized as: + + CommandLine::Args Args[] = + {{'q',"quiet","apt::get::quiet",CommandLine::IntLevel}, + {0,0,0,0}}; + + The flags mean, + HasArg - Means the argument has a value + IntLevel - Means the argument is an integer level indication, the + following -qqqq (+3) -q5 (=5) -q=5 (=5) are valid + Boolean - Means it is true/false or yes/no. + -d (true) --no-d (false) --yes-d (true) + --long (true) --no-long (false) --yes-long (true) + -d=yes (true) -d=no (false) Words like enable, disable, + true false, yes no and on off are recognized in logical + places. + InvBoolean - Same as boolean but the case with no specified sense + (first case) is set to false. + ConfigFile - Means this flag should be interprited as the name of + a config file to read in at this point in option processing. + Implies HasArg. + ArbItem - Means the item is an arbitrary configuration string of + the form item=value, where item is passed directly + to the configuration class. + The default, if the flags are 0 is to use Boolean + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_CMNDLINE_H +#define PKGLIB_CMNDLINE_H + +#include <apt-pkg/macros.h> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/configuration.h> +#endif + +class Configuration; + +class CommandLine +{ + public: + struct Args; + struct Dispatch; + struct DispatchWithHelp; + + protected: + + Args *ArgList; + Configuration *Conf; + bool HandleOpt(int &I,int argc,const char *argv[], + const char *&Opt,Args *A,bool PreceedeMatch = false); + void static SaveInConfig(unsigned int const &argc, char const * const * const argv); + + public: + + enum AFlags + { + HasArg = (1 << 0), + IntLevel = (1 << 1), + Boolean = (1 << 2), + InvBoolean = (1 << 3), + ConfigFile = (1 << 4) | HasArg, + ArbItem = (1 << 5) | HasArg + }; + + const char **FileList; + + bool Parse(int argc,const char **argv); + void ShowHelp(); + unsigned int FileSize() const APT_PURE; + // FIXME: merge on next ABI break + bool DispatchArg(Dispatch *List,bool NoMatch = true); + bool DispatchArg(Dispatch const * const List,bool NoMatch = true); + + static char const * GetCommand(Dispatch const * const Map, + unsigned int const argc, char const * const * const argv) APT_PURE; + + static CommandLine::Args MakeArgs(char ShortOpt, char const *LongOpt, + char const *ConfName, unsigned long Flags) APT_PURE; + + CommandLine(); + CommandLine(Args *AList,Configuration *Conf); + ~CommandLine(); +}; + +struct CommandLine::Args +{ + char ShortOpt; + const char *LongOpt; + const char *ConfName; + unsigned long Flags; + + inline bool end() {return ShortOpt == 0 && LongOpt == 0;}; + inline bool IsBoolean() {return Flags == 0 || (Flags & (Boolean|InvBoolean)) != 0;}; +}; + +struct CommandLine::Dispatch +{ + const char *Match; + bool (*Handler)(CommandLine &); +}; + +#endif diff --git a/apt-pkg/contrib/configuration.cc b/apt-pkg/contrib/configuration.cc new file mode 100644 index 0000000..997ef74 --- /dev/null +++ b/apt-pkg/contrib/configuration.cc @@ -0,0 +1,1209 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Configuration Class + + This class provides a configuration file and command line parser + for a tree-oriented configuration environment. All runtime configuration + is stored in here. + + This source is placed in the Public Domain, do with it what you will + It was originally written by Jason Gunthorpe <jgg@debian.org>. + + ##################################################################### */ + /*}}}*/ +// Include files /*{{{*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/strutl.h> + +#include <ctype.h> +#include <regex.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <algorithm> +#include <fstream> +#include <iterator> +#include <sstream> +#include <stack> +#include <string> +#include <unordered_map> +#include <vector> + +#include <apti18n.h> + +using namespace std; + /*}}}*/ + +Configuration *_config = new Configuration; + +/* TODO: This config verification shouldn't be using a static variable + but a Cnf-member – but that would need ABI breaks and stuff and for now + that really is an apt-dev-only tool, so it isn't that bad that it is + unusable and allaround a bit strange */ +enum class APT_HIDDEN ConfigType { UNDEFINED, INT, BOOL, STRING, STRING_OR_BOOL, STRING_OR_LIST, FILE, DIR, LIST, PROGRAM_PATH = FILE }; +APT_HIDDEN std::unordered_map<std::string, ConfigType> apt_known_config {}; +static std::string getConfigTypeString(ConfigType const type) /*{{{*/ +{ + switch (type) + { + case ConfigType::UNDEFINED: return "UNDEFINED"; + case ConfigType::INT: return "INT"; + case ConfigType::BOOL: return "BOOL"; + case ConfigType::STRING: return "STRING"; + case ConfigType::STRING_OR_BOOL: return "STRING_OR_BOOL"; + case ConfigType::FILE: return "FILE"; + case ConfigType::DIR: return "DIR"; + case ConfigType::LIST: return "LIST"; + case ConfigType::STRING_OR_LIST: return "STRING_OR_LIST"; + } + return "UNKNOWN"; +} + /*}}}*/ +static ConfigType getConfigType(std::string const &type) /*{{{*/ +{ + if (type == "<INT>") + return ConfigType::INT; + else if (type == "<BOOL>") + return ConfigType::BOOL; + else if (type == "<STRING>") + return ConfigType::STRING; + else if (type == "<STRING_OR_BOOL>") + return ConfigType::STRING_OR_BOOL; + else if (type == "<FILE>") + return ConfigType::FILE; + else if (type == "<DIR>") + return ConfigType::DIR; + else if (type == "<LIST>") + return ConfigType::LIST; + else if (type == "<STRING_OR_LIST>") + return ConfigType::STRING_OR_LIST; + else if (type == "<PROGRAM_PATH>") + return ConfigType::PROGRAM_PATH; + return ConfigType::UNDEFINED; +} + /*}}}*/ +// checkFindConfigOptionType - workhorse of option checking /*{{{*/ +static void checkFindConfigOptionTypeInternal(std::string name, ConfigType const type) +{ + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + auto known = apt_known_config.find(name); + if (known == apt_known_config.cend()) + { + auto const rcolon = name.rfind(':'); + if (rcolon != std::string::npos) + { + known = apt_known_config.find(name.substr(0, rcolon) + ":*"); + if (known == apt_known_config.cend()) + { + auto const parts = StringSplit(name, "::"); + size_t psize = parts.size(); + if (psize > 1) + { + for (size_t max = psize; max != 1; --max) + { + std::ostringstream os; + std::copy(parts.begin(), parts.begin() + max, std::ostream_iterator<std::string>(os, "::")); + os << "**"; + known = apt_known_config.find(os.str()); + if (known != apt_known_config.cend() && known->second == ConfigType::UNDEFINED) + return; + } + for (size_t max = psize - 1; max != 1; --max) + { + std::ostringstream os; + std::copy(parts.begin(), parts.begin() + max - 1, std::ostream_iterator<std::string>(os, "::")); + os << "*::"; + std::copy(parts.begin() + max + 1, parts.end() - 1, std::ostream_iterator<std::string>(os, "::")); + os << *(parts.end() - 1); + known = apt_known_config.find(os.str()); + if (known != apt_known_config.cend()) + break; + } + } + } + } + } + if (known == apt_known_config.cend()) + _error->Warning("Using unknown config option »%s« of type %s", + name.c_str(), getConfigTypeString(type).c_str()); + else if (known->second != type) + { + if (known->second == ConfigType::DIR && type == ConfigType::FILE) + ; // implementation detail + else if (type == ConfigType::STRING && (known->second == ConfigType::FILE || known->second == ConfigType::DIR)) + ; // TODO: that might be an error or not, we will figure this out later + else if (known->second == ConfigType::STRING_OR_BOOL && (type == ConfigType::BOOL || type == ConfigType::STRING)) + ; + else if (known->second == ConfigType::STRING_OR_LIST && (type == ConfigType::LIST || type == ConfigType::STRING)) + ; + else + _error->Warning("Using config option »%s« of type %s as a type %s", + name.c_str(), getConfigTypeString(known->second).c_str(), getConfigTypeString(type).c_str()); + } +} +static void checkFindConfigOptionType(char const * const name, ConfigType const type) +{ + if (apt_known_config.empty()) + return; + checkFindConfigOptionTypeInternal(name, type); +} + /*}}}*/ +static bool LoadConfigurationIndex(std::string const &filename) /*{{{*/ +{ + apt_known_config.clear(); + if (filename.empty()) + return true; + Configuration Idx; + if (ReadConfigFile(Idx, filename) == false) + return false; + + Configuration::Item const * Top = Idx.Tree(nullptr); + if (unlikely(Top == nullptr)) + return false; + + do { + if (Top->Value.empty() == false) + { + std::string fulltag = Top->FullTag(); + std::transform(fulltag.begin(), fulltag.end(), fulltag.begin(), ::tolower); + apt_known_config.emplace(std::move(fulltag), getConfigType(Top->Value)); + } + + if (Top->Child != nullptr) + { + Top = Top->Child; + continue; + } + + while (Top != nullptr && Top->Next == nullptr) + Top = Top->Parent; + if (Top != nullptr) + Top = Top->Next; + } while (Top != nullptr); + + return true; +} + /*}}}*/ + +// Configuration::Configuration - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +Configuration::Configuration() : ToFree(true) +{ + Root = new Item; +} +Configuration::Configuration(const Item *Root) : Root((Item *)Root), ToFree(false) +{ +} + /*}}}*/ +// Configuration::~Configuration - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +Configuration::~Configuration() +{ + if (ToFree == false) + return; + + Item *Top = Root; + for (; Top != 0;) + { + if (Top->Child != 0) + { + Top = Top->Child; + continue; + } + + while (Top != 0 && Top->Next == 0) + { + Item *Parent = Top->Parent; + delete Top; + Top = Parent; + } + if (Top != 0) + { + Item *Next = Top->Next; + delete Top; + Top = Next; + } + } +} + /*}}}*/ +// Configuration::Lookup - Lookup a single item /*{{{*/ +// --------------------------------------------------------------------- +/* This will lookup a single item by name below another item. It is a + helper function for the main lookup function */ +Configuration::Item *Configuration::Lookup(Item *Head,const char *S, + unsigned long const &Len,bool const &Create) +{ + int Res = 1; + Item *I = Head->Child; + Item **Last = &Head->Child; + + // Empty strings match nothing. They are used for lists. + if (Len != 0) + { + for (; I != 0; Last = &I->Next, I = I->Next) + if (Len == I->Tag.length() && (Res = stringcasecmp(I->Tag,S,S + Len)) == 0) + break; + } + else + for (; I != 0; Last = &I->Next, I = I->Next); + + if (Res == 0) + return I; + if (Create == false) + return 0; + + I = new Item; + I->Tag.assign(S,Len); + I->Next = *Last; + I->Parent = Head; + *Last = I; + return I; +} + /*}}}*/ +// Configuration::Lookup - Lookup a fully scoped item /*{{{*/ +// --------------------------------------------------------------------- +/* This performs a fully scoped lookup of a given name, possibly creating + new items */ +Configuration::Item *Configuration::Lookup(const char *Name,bool const &Create) +{ + if (Name == 0) + return Root->Child; + + const char *Start = Name; + const char *End = Start + strlen(Name); + const char *TagEnd = Name; + Item *Itm = Root; + for (; End - TagEnd >= 2; TagEnd++) + { + if (TagEnd[0] == ':' && TagEnd[1] == ':') + { + Itm = Lookup(Itm,Start,TagEnd - Start,Create); + if (Itm == 0) + return 0; + TagEnd = Start = TagEnd + 2; + } + } + + // This must be a trailing ::, we create unique items in a list + if (End - Start == 0) + { + if (Create == false) + return 0; + } + + Itm = Lookup(Itm,Start,End - Start,Create); + return Itm; +} + /*}}}*/ +// Configuration::Find - Find a value /*{{{*/ +// --------------------------------------------------------------------- +/* */ +string Configuration::Find(const char *Name,const char *Default) const +{ + checkFindConfigOptionType(Name, ConfigType::STRING); + const Item *Itm = Lookup(Name); + if (Itm == 0 || Itm->Value.empty() == true) + { + if (Default == 0) + return ""; + else + return Default; + } + + return Itm->Value; +} + /*}}}*/ +// Configuration::FindFile - Find a Filename /*{{{*/ +// --------------------------------------------------------------------- +/* Directories are stored as the base dir in the Parent node and the + sub directory in sub nodes with the final node being the end filename + */ +string Configuration::FindFile(const char *Name,const char *Default) const +{ + checkFindConfigOptionType(Name, ConfigType::FILE); + const Item *RootItem = Lookup("RootDir"); + std::string result = (RootItem == 0) ? "" : RootItem->Value; + if(result.empty() == false && result[result.size() - 1] != '/') + result.push_back('/'); + + const Item *Itm = Lookup(Name); + if (Itm == 0 || Itm->Value.empty() == true) + { + if (Default != 0) + result.append(Default); + } + else + { + string val = Itm->Value; + while (Itm->Parent != 0) + { + if (Itm->Parent->Value.empty() == true) + { + Itm = Itm->Parent; + continue; + } + + // Absolute + if (val.length() >= 1 && val[0] == '/') + { + if (val.compare(0, 9, "/dev/null") == 0) + val.erase(9); + break; + } + + // ~/foo or ./foo + if (val.length() >= 2 && (val[0] == '~' || val[0] == '.') && val[1] == '/') + break; + + // ../foo + if (val.length() >= 3 && val[0] == '.' && val[1] == '.' && val[2] == '/') + break; + + if (Itm->Parent->Value.end()[-1] != '/') + val.insert(0, "/"); + + val.insert(0, Itm->Parent->Value); + Itm = Itm->Parent; + } + result.append(val); + } + return flNormalize(result); +} + /*}}}*/ +// Configuration::FindDir - Find a directory name /*{{{*/ +// --------------------------------------------------------------------- +/* This is like findfile execept the result is terminated in a / */ +string Configuration::FindDir(const char *Name,const char *Default) const +{ + checkFindConfigOptionType(Name, ConfigType::DIR); + string Res = FindFile(Name,Default); + if (Res.end()[-1] != '/') + { + size_t const found = Res.rfind("/dev/null"); + if (found != string::npos && found == Res.size() - 9) + return Res; // /dev/null returning + return Res + '/'; + } + return Res; +} + /*}}}*/ +// Configuration::FindVector - Find a vector of values /*{{{*/ +// --------------------------------------------------------------------- +/* Returns a vector of config values under the given item */ +vector<string> Configuration::FindVector(const char *Name, std::string const &Default, bool const Keys) const +{ + checkFindConfigOptionType(Name, ConfigType::LIST); + vector<string> Vec; + const Item *Top = Lookup(Name); + if (Top == NULL) + return VectorizeString(Default, ','); + + if (Top->Value.empty() == false) + return VectorizeString(Top->Value, ','); + + Item *I = Top->Child; + while(I != NULL) + { + Vec.push_back(Keys ? I->Tag : I->Value); + I = I->Next; + } + if (Vec.empty() == true) + return VectorizeString(Default, ','); + + return Vec; +} + /*}}}*/ +// Configuration::FindI - Find an integer value /*{{{*/ +// --------------------------------------------------------------------- +/* */ +int Configuration::FindI(const char *Name,int const &Default) const +{ + checkFindConfigOptionType(Name, ConfigType::INT); + const Item *Itm = Lookup(Name); + if (Itm == 0 || Itm->Value.empty() == true) + return Default; + + char *End; + int Res = strtol(Itm->Value.c_str(),&End,0); + if (End == Itm->Value.c_str()) + return Default; + + return Res; +} + /*}}}*/ +// Configuration::FindB - Find a boolean type /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool Configuration::FindB(const char *Name,bool const &Default) const +{ + checkFindConfigOptionType(Name, ConfigType::BOOL); + const Item *Itm = Lookup(Name); + if (Itm == 0 || Itm->Value.empty() == true) + return Default; + + return StringToBool(Itm->Value,Default); +} + /*}}}*/ +// Configuration::FindAny - Find an arbitrary type /*{{{*/ +// --------------------------------------------------------------------- +/* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */ +string Configuration::FindAny(const char *Name,const char *Default) const +{ + string key = Name; + char type = 0; + + if (key.size() > 2 && key.end()[-2] == '/') + { + type = key.end()[-1]; + key.resize(key.size() - 2); + } + + switch (type) + { + // file + case 'f': + return FindFile(key.c_str(), Default); + + // directory + case 'd': + return FindDir(key.c_str(), Default); + + // bool + case 'b': + return FindB(key, Default) ? "true" : "false"; + + // int + case 'i': + { + char buf[16]; + snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 )); + return buf; + } + } + + // fallback + return Find(Name, Default); +} + /*}}}*/ +// Configuration::CndSet - Conditinal Set a value /*{{{*/ +// --------------------------------------------------------------------- +/* This will not overwrite */ +void Configuration::CndSet(const char *Name,const string &Value) +{ + Item *Itm = Lookup(Name,true); + if (Itm == 0) + return; + if (Itm->Value.empty() == true) + Itm->Value = Value; +} + /*}}}*/ +// Configuration::Set - Set an integer value /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void Configuration::CndSet(const char *Name,int const Value) +{ + Item *Itm = Lookup(Name,true); + if (Itm == 0 || Itm->Value.empty() == false) + return; + char S[300]; + snprintf(S,sizeof(S),"%i",Value); + Itm->Value = S; +} + /*}}}*/ +// Configuration::Set - Set a value /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void Configuration::Set(const char *Name,const string &Value) +{ + Item *Itm = Lookup(Name,true); + if (Itm == 0) + return; + Itm->Value = Value; +} + /*}}}*/ +// Configuration::Set - Set an integer value /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void Configuration::Set(const char *Name,int const &Value) +{ + Item *Itm = Lookup(Name,true); + if (Itm == 0) + return; + char S[300]; + snprintf(S,sizeof(S),"%i",Value); + Itm->Value = S; +} + /*}}}*/ +// Configuration::Clear - Clear an single value from a list /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void Configuration::Clear(string const &Name, int const &Value) +{ + char S[300]; + snprintf(S,sizeof(S),"%i",Value); + Clear(Name, S); +} + /*}}}*/ +// Configuration::Clear - Clear an single value from a list /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void Configuration::Clear(string const &Name, string const &Value) +{ + Item *Top = Lookup(Name.c_str(),false); + if (Top == 0 || Top->Child == 0) + return; + + Item *Tmp, *Prev, *I; + Prev = I = Top->Child; + + while(I != NULL) + { + if(I->Value == Value) + { + Tmp = I; + // was first element, point parent to new first element + if(Top->Child == Tmp) + Top->Child = I->Next; + I = I->Next; + Prev->Next = I; + delete Tmp; + } else { + Prev = I; + I = I->Next; + } + } + +} + /*}}}*/ +// Configuration::Clear - Clear everything /*{{{*/ +// --------------------------------------------------------------------- +void Configuration::Clear() +{ + const Configuration::Item *Top = Tree(0); + while( Top != 0 ) + { + Clear(Top->FullTag()); + Top = Top->Next; + } +} + /*}}}*/ +// Configuration::Clear - Clear an entire tree /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void Configuration::Clear(string const &Name) +{ + Item *Top = Lookup(Name.c_str(),false); + if (Top == 0) + return; + + Top->Value.clear(); + Item *Stop = Top; + Top = Top->Child; + Stop->Child = 0; + for (; Top != 0;) + { + if (Top->Child != 0) + { + Top = Top->Child; + continue; + } + + while (Top != 0 && Top->Next == 0) + { + Item *Tmp = Top; + Top = Top->Parent; + delete Tmp; + + if (Top == Stop) + return; + } + + Item *Tmp = Top; + if (Top != 0) + Top = Top->Next; + delete Tmp; + } +} + /*}}}*/ +void Configuration::MoveSubTree(char const * const OldRootName, char const * const NewRootName)/*{{{*/ +{ + // prevent NewRoot being a subtree of OldRoot + if (OldRootName == nullptr) + return; + if (NewRootName != nullptr) + { + if (strcmp(OldRootName, NewRootName) == 0) + return; + std::string const oldroot = std::string(OldRootName) + "::"; + if (strcasestr(NewRootName, oldroot.c_str()) != NULL) + return; + } + + Item * Top; + Item const * const OldRoot = Top = Lookup(OldRootName, false); + if (Top == nullptr) + return; + std::string NewRoot; + if (NewRootName != nullptr) + NewRoot.append(NewRootName).append("::"); + + Top->Value.clear(); + Item * const Stop = Top; + Top = Top->Child; + Stop->Child = 0; + for (; Top != 0;) + { + if (Top->Child != 0) + { + Top = Top->Child; + continue; + } + + while (Top != 0 && Top->Next == 0) + { + Set(NewRoot + Top->FullTag(OldRoot), Top->Value); + Item const * const Tmp = Top; + Top = Top->Parent; + delete Tmp; + + if (Top == Stop) + return; + } + + Set(NewRoot + Top->FullTag(OldRoot), Top->Value); + Item const * const Tmp = Top; + if (Top != 0) + Top = Top->Next; + delete Tmp; + } +} + /*}}}*/ +// Configuration::Exists - Returns true if the Name exists /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool Configuration::Exists(const char *Name) const +{ + const Item *Itm = Lookup(Name); + if (Itm == 0) + return false; + return true; +} + /*}}}*/ +// Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/ +// --------------------------------------------------------------------- +/* qualified by /[fdbi] exists */ +bool Configuration::ExistsAny(const char *Name) const +{ + string key = Name; + + if (key.size() > 2 && key.end()[-2] == '/') + { + if (key.find_first_of("fdbi",key.size()-1) < key.size()) + { + key.resize(key.size() - 2); + if (Exists(key.c_str())) + return true; + } + else + { + _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]); + } + } + return Exists(Name); +} + /*}}}*/ +// Configuration::Dump - Dump the config /*{{{*/ +// --------------------------------------------------------------------- +/* Dump the entire configuration space */ +void Configuration::Dump(ostream& str) +{ + Dump(str, NULL, "%F \"%v\";\n", true); +} +void Configuration::Dump(ostream& str, char const * const root, + char const * const formatstr, bool const emptyValue) +{ + const Configuration::Item* Top = Tree(root); + if (Top == 0) + return; + const Configuration::Item* const Root = (root == NULL) ? NULL : Top; + std::vector<std::string> const format = VectorizeString(formatstr, '%'); + + /* Write out all of the configuration directives by walking the + configuration tree */ + do { + if (emptyValue == true || Top->Value.empty() == emptyValue) + { + std::vector<std::string>::const_iterator f = format.begin(); + str << *f; + for (++f; f != format.end(); ++f) + { + if (f->empty() == true) + { + ++f; + str << '%' << *f; + continue; + } + char const type = (*f)[0]; + if (type == 'f') + str << Top->FullTag(); + else if (type == 't') + str << Top->Tag; + else if (type == 'v') + str << Top->Value; + else if (type == 'F') + str << QuoteString(Top->FullTag(), "=\"\n"); + else if (type == 'T') + str << QuoteString(Top->Tag, "=\"\n"); + else if (type == 'V') + str << QuoteString(Top->Value, "=\"\n"); + else if (type == 'n') + str << "\n"; + else if (type == 'N') + str << "\t"; + else + str << '%' << type; + str << f->c_str() + 1; + } + } + + if (Top->Child != 0) + { + Top = Top->Child; + continue; + } + + while (Top != 0 && Top->Next == 0) + Top = Top->Parent; + if (Top != 0) + Top = Top->Next; + + if (Root != NULL) + { + const Configuration::Item* I = Top; + while(I != 0) + { + if (I == Root) + break; + else + I = I->Parent; + } + if (I == 0) + break; + } + } while (Top != 0); +} + /*}}}*/ + +// Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/ +// --------------------------------------------------------------------- +/* Stop sets an optional max recursion depth if this item is being viewed as + part of a sub tree. */ +string Configuration::Item::FullTag(const Item *Stop) const +{ + if (Parent == 0 || Parent->Parent == 0 || Parent == Stop) + return Tag; + return Parent->FullTag(Stop) + "::" + Tag; +} + /*}}}*/ + +// ReadConfigFile - Read a configuration file /*{{{*/ +// --------------------------------------------------------------------- +/* The configuration format is very much like the named.conf format + used in bind8, in fact this routine can parse most named.conf files. + Sectional config files are like bind's named.conf where there are + sections like 'zone "foo.org" { .. };' This causes each section to be + added in with a tag like "zone::foo.org" instead of being split + tag/value. AsSectional enables Sectional parsing.*/ +static void leaveCurrentScope(std::stack<std::string> &Stack, std::string &ParentTag) +{ + if (Stack.empty()) + ParentTag.clear(); + else + { + ParentTag = Stack.top(); + Stack.pop(); + } +} +bool ReadConfigFile(Configuration &Conf,const string &FName,bool const &AsSectional, + unsigned const &Depth) +{ + // Open the stream for reading + FileFd F; + if (OpenConfigurationFileFd(FName, F) == false) + return false; + + string LineBuffer; + std::stack<std::string> Stack; + + // Parser state + string ParentTag; + + int CurLine = 0; + bool InComment = false; + while (F.Eof() == false) + { + // The raw input line. + std::string Input; + if (F.ReadLine(Input) == false) + Input.clear(); + // The input line with comments stripped. + std::string Fragment; + + // Expand tabs in the input line and remove leading and trailing + // whitespace. + { + const int BufferSize = Input.size() * 8 + 1; + char *Buffer = new char[BufferSize]; + try + { + memcpy(Buffer, Input.c_str(), Input.size() + 1); + + _strtabexpand(Buffer, BufferSize); + _strstrip(Buffer); + Input = Buffer; + } + catch(...) + { + delete[] Buffer; + throw; + } + delete[] Buffer; + } + CurLine++; + + // Now strip comments; if the whole line is contained in a + // comment, skip this line. + + // The first meaningful character in the current fragment; will + // be adjusted below as we remove bytes from the front. + std::string::const_iterator Start = Input.begin(); + // The last meaningful character in the current fragment. + std::string::const_iterator End = Input.end(); + + // Multi line comment + if (InComment == true) + { + for (std::string::const_iterator I = Start; + I != End; ++I) + { + if (*I == '*' && I + 1 != End && I[1] == '/') + { + Start = I + 2; + InComment = false; + break; + } + } + if (InComment == true) + continue; + } + + // Discard single line comments + bool InQuote = false; + for (std::string::const_iterator I = Start; + I != End; ++I) + { + if (*I == '"') + InQuote = !InQuote; + if (InQuote == true) + continue; + + if ((*I == '/' && I + 1 != End && I[1] == '/') || + (*I == '#' && strcmp(string(I,I+6).c_str(),"#clear") != 0 && + strcmp(string(I,I+8).c_str(),"#include") != 0 && + strcmp(string(I,I+strlen("#x-apt-configure-index")).c_str(), "#x-apt-configure-index") != 0)) + { + End = I; + break; + } + } + + // Look for multi line comments and build up the + // fragment. + Fragment.reserve(End - Start); + InQuote = false; + for (std::string::const_iterator I = Start; + I != End; ++I) + { + if (*I == '"') + InQuote = !InQuote; + if (InQuote == true) + Fragment.push_back(*I); + else if (*I == '/' && I + 1 != End && I[1] == '*') + { + InComment = true; + for (std::string::const_iterator J = I; + J != End; ++J) + { + if (*J == '*' && J + 1 != End && J[1] == '/') + { + // Pretend we just finished walking over the + // comment, and don't add anything to the output + // fragment. + I = J + 1; + InComment = false; + break; + } + } + + if (InComment == true) + break; + } + else + Fragment.push_back(*I); + } + + // Skip blank lines. + if (Fragment.empty()) + continue; + + // The line has actual content; interpret what it means. + InQuote = false; + Start = Fragment.begin(); + End = Fragment.end(); + for (std::string::const_iterator I = Start; + I != End; ++I) + { + if (*I == '"') + InQuote = !InQuote; + + if (InQuote == false && (*I == '{' || *I == ';' || *I == '}')) + { + // Put the last fragment into the buffer + std::string::const_iterator NonWhitespaceStart = Start; + std::string::const_iterator NonWhitespaceStop = I; + for (; NonWhitespaceStart != I && isspace(*NonWhitespaceStart) != 0; ++NonWhitespaceStart) + ; + for (; NonWhitespaceStop != NonWhitespaceStart && isspace(NonWhitespaceStop[-1]) != 0; --NonWhitespaceStop) + ; + if (LineBuffer.empty() == false && NonWhitespaceStop - NonWhitespaceStart != 0) + LineBuffer += ' '; + LineBuffer += string(NonWhitespaceStart, NonWhitespaceStop); + + // Drop this from the input string, saving the character + // that terminated the construct we just closed. (i.e., a + // brace or a semicolon) + char TermChar = *I; + Start = I + 1; + + // Syntax Error + if (TermChar == '{' && LineBuffer.empty() == true) + return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine); + + // No string on this line + if (LineBuffer.empty() == true) + { + if (TermChar == '}') + leaveCurrentScope(Stack, ParentTag); + continue; + } + + // Parse off the tag + string Tag; + const char *Pos = LineBuffer.c_str(); + if (ParseQuoteWord(Pos,Tag) == false) + return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine); + + // Parse off the word + string Word; + bool NoWord = false; + if (ParseCWord(Pos,Word) == false && + ParseQuoteWord(Pos,Word) == false) + { + if (TermChar != '{') + { + Word = Tag; + Tag = ""; + } + else + NoWord = true; + } + if (strlen(Pos) != 0) + return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine); + + // Go down a level + if (TermChar == '{') + { + Stack.push(ParentTag); + + /* Make sectional tags incorporate the section into the + tag string */ + if (AsSectional == true && Word.empty() == false) + { + Tag.append("::").append(Word); + Word.clear(); + } + + if (ParentTag.empty() == true) + ParentTag = Tag; + else + ParentTag.append("::").append(Tag); + Tag.clear(); + } + + // Generate the item name + string Item; + if (ParentTag.empty() == true) + Item = Tag; + else + { + if (TermChar != '{' || Tag.empty() == false) + Item = ParentTag + "::" + Tag; + else + Item = ParentTag; + } + + // Specials + if (Tag.length() >= 1 && Tag[0] == '#') + { + if (ParentTag.empty() == false) + return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine); + Tag.erase(Tag.begin()); + if (Tag == "clear") + Conf.Clear(Word); + else if (Tag == "include") + { + if (Depth > 10) + return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine); + if (Word.length() > 2 && Word.end()[-1] == '/') + { + if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false) + return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine); + } + else + { + if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false) + return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine); + } + } + else if (Tag == "x-apt-configure-index") + { + if (LoadConfigurationIndex(Word) == false) + return _error->Warning("Loading the configure index %s in file %s:%u failed!", Word.c_str(), FName.c_str(), CurLine); + } + else + return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str()); + } + else if (Tag.empty() == true && NoWord == false && Word == "#clear") + return _error->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName.c_str(),CurLine); + else + { + // Set the item in the configuration class + if (NoWord == false) + Conf.Set(Item,Word); + } + + // Empty the buffer + LineBuffer.clear(); + + // Move up a tag, but only if there is no bit to parse + if (TermChar == '}') + leaveCurrentScope(Stack, ParentTag); + } + } + + // Store the remaining text, if any, in the current line buffer. + + // NB: could change this to use string-based operations; I'm + // using strstrip now to ensure backwards compatibility. + // -- dburrows 2008-04-01 + { + char *Buffer = new char[End - Start + 1]; + try + { + std::copy(Start, End, Buffer); + Buffer[End - Start] = '\0'; + + const char *Stripd = _strstrip(Buffer); + if (*Stripd != 0 && LineBuffer.empty() == false) + LineBuffer += " "; + LineBuffer += Stripd; + } + catch(...) + { + delete[] Buffer; + throw; + } + delete[] Buffer; + } + } + + if (LineBuffer.empty() == false) + return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine); + return true; +} + /*}}}*/ +// ReadConfigDir - Read a directory of config files /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool ReadConfigDir(Configuration &Conf,const string &Dir, + bool const &AsSectional, unsigned const &Depth) +{ + bool good = true; + for (auto const &I : GetListOfFilesInDir(Dir, "conf", true, true)) + good = ReadConfigFile(Conf, I, AsSectional, Depth) && good; + return good; +} + /*}}}*/ +// MatchAgainstConfig Constructor /*{{{*/ +Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config) +{ + std::vector<std::string> const strings = _config->FindVector(Config); + for (std::vector<std::string>::const_iterator s = strings.begin(); + s != strings.end(); ++s) + { + regex_t *p = new regex_t; + if (regcomp(p, s->c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB) == 0) + patterns.push_back(p); + else + { + regfree(p); + delete p; + _error->Warning("Invalid regular expression '%s' in configuration " + "option '%s' will be ignored.", + s->c_str(), Config); + continue; + } + } + if (strings.empty() == true) + patterns.push_back(NULL); +} + /*}}}*/ +// MatchAgainstConfig Destructor /*{{{*/ +Configuration::MatchAgainstConfig::~MatchAgainstConfig() +{ + clearPatterns(); +} +void Configuration::MatchAgainstConfig::clearPatterns() +{ + for(std::vector<regex_t *>::const_iterator p = patterns.begin(); + p != patterns.end(); ++p) + { + if (*p == NULL) continue; + regfree(*p); + delete *p; + } + patterns.clear(); +} + /*}}}*/ +// MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/ +bool Configuration::MatchAgainstConfig::Match(char const * str) const +{ + for(std::vector<regex_t *>::const_iterator p = patterns.begin(); + p != patterns.end(); ++p) + if (*p != NULL && regexec(*p, str, 0, 0, 0) == 0) + return true; + + return false; +} + /*}}}*/ diff --git a/apt-pkg/contrib/configuration.h b/apt-pkg/contrib/configuration.h new file mode 100644 index 0000000..2a3ae1a --- /dev/null +++ b/apt-pkg/contrib/configuration.h @@ -0,0 +1,155 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Configuration Class + + This class provides a configuration file and command line parser + for a tree-oriented configuration environment. All runtime configuration + is stored in here. + + Each configuration name is given as a fully scoped string such as + Foo::Bar + And has associated with it a text string. The Configuration class only + provides storage and lookup for this tree, other classes provide + configuration file formats (and parsers/emitters if needed). + + Most things can get by quite happily with, + cout << _config->Find("Foo::Bar") << endl; + + A special extension, support for ordered lists is provided by using the + special syntax, "block::list::" the trailing :: designates the + item as a list. To access the list you must use the tree function on + "block::list". + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_CONFIGURATION_H +#define PKGLIB_CONFIGURATION_H + +#include <regex.h> + +#include <iostream> +#include <string> +#include <vector> + +#include <apt-pkg/macros.h> + +#ifndef APT_8_CLEANER_HEADERS +using std::string; +#endif + +class Configuration +{ + public: + + struct Item + { + std::string Value; + std::string Tag; + Item *Parent; + Item *Child; + Item *Next; + + std::string FullTag(const Item *Stop = 0) const; + + Item() : Parent(0), Child(0), Next(0) {}; + }; + + private: + + Item *Root; + bool ToFree; + + Item *Lookup(Item *Head,const char *S,unsigned long const &Len,bool const &Create); + Item *Lookup(const char *Name,const bool &Create); + inline const Item *Lookup(const char *Name) const + { + return const_cast<Configuration *>(this)->Lookup(Name,false); + } + + public: + + std::string Find(const char *Name,const char *Default = 0) const; + std::string Find(std::string const &Name,const char *Default = 0) const {return Find(Name.c_str(),Default);}; + std::string Find(std::string const &Name, std::string const &Default) const {return Find(Name.c_str(),Default.c_str());}; + std::string FindFile(const char *Name,const char *Default = 0) const; + std::string FindDir(const char *Name,const char *Default = 0) const; + /** return a list of child options + * + * Options like Acquire::Languages are handled as lists which + * can be overridden and have a default. For the later two a comma + * separated list of values is supported. + * + * \param Name of the parent node + * \param Default list of values separated by commas */ + std::vector<std::string> FindVector(const char *Name, std::string const &Default = "", bool const Keys = false) const; + std::vector<std::string> FindVector(std::string const &Name, std::string const &Default = "", bool const Keys = false) const { return FindVector(Name.c_str(), Default, Keys); }; + + int FindI(const char *Name,int const &Default = 0) const; + int FindI(std::string const &Name,int const &Default = 0) const {return FindI(Name.c_str(),Default);}; + bool FindB(const char *Name,bool const &Default = false) const; + bool FindB(std::string const &Name,bool const &Default = false) const {return FindB(Name.c_str(),Default);}; + std::string FindAny(const char *Name,const char *Default = 0) const; + + inline void Set(const std::string &Name,const std::string &Value) {Set(Name.c_str(),Value);}; + void CndSet(const char *Name,const std::string &Value); + void CndSet(const char *Name,const int Value); + void Set(const char *Name,const std::string &Value); + void Set(const char *Name,const int &Value); + + inline bool Exists(const std::string &Name) const {return Exists(Name.c_str());}; + bool Exists(const char *Name) const; + bool ExistsAny(const char *Name) const; + + void MoveSubTree(char const * const OldRoot, char const * const NewRoot); + + // clear a whole tree + void Clear(const std::string &Name); + void Clear(); + + // remove a certain value from a list (e.g. the list of "APT::Keep-Fds") + void Clear(std::string const &List, std::string const &Value); + void Clear(std::string const &List, int const &Value); + + inline const Item *Tree(const char *Name) const {return Lookup(Name);}; + + inline void Dump() { Dump(std::clog); }; + void Dump(std::ostream& str); + void Dump(std::ostream& str, char const * const root, + char const * const format, bool const emptyValue); + + Configuration(const Item *Root); + Configuration(); + ~Configuration(); + + /** \brief match a string against a configurable list of patterns */ + class MatchAgainstConfig + { + std::vector<regex_t *> patterns; + APT_HIDDEN void clearPatterns(); + + public: + MatchAgainstConfig(char const * Config); + virtual ~MatchAgainstConfig(); + + /** \brief Returns \b true for a string matching one of the patterns */ + bool Match(char const * str) const; + bool Match(std::string const &str) const { return Match(str.c_str()); }; + + /** \brief returns if the matcher setup was successful */ + bool wasConstructedSuccessfully() const { return patterns.empty() == false; } + }; +}; + +extern Configuration *_config; + +bool ReadConfigFile(Configuration &Conf,const std::string &FName, + bool const &AsSectional = false, + unsigned const &Depth = 0); + +bool ReadConfigDir(Configuration &Conf,const std::string &Dir, + bool const &AsSectional = false, + unsigned const &Depth = 0); + +#endif diff --git a/apt-pkg/contrib/crc-16.cc b/apt-pkg/contrib/crc-16.cc new file mode 100644 index 0000000..2e396ab --- /dev/null +++ b/apt-pkg/contrib/crc-16.cc @@ -0,0 +1,77 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + CRC16 - Compute a 16bit crc very quickly + + This was ripped out of the linux 2.2 kernel source (irda/crc.c) and + is credited to ppp.c by Michael Callahan <callahan@maths.ox.ac.uk> and + Al Longyear <longyear@netcom.com> + + Modified by Jason Gunthorpe <jgg@debian.org> to fit the local coding + style, this code is believed to be in the Public Domain. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/crc-16.h> + /*}}}*/ + +/* + * This mysterious table is just the CRC of each possible byte. It can be + * computed using the standard bit-at-a-time methods. The polynomial can + * be seen in entry 128, 0x8408. This corresponds to x^0 + x^5 + x^12. + * Add the implicit x^16, and you have the standard CRC-CCITT. + */ +static unsigned short const crc16_table[256] = +{ + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 +}; + +/* Recompute the FCS with one more character appended. */ +#define CalcFCS(fcs, c) (((fcs) >> 8) ^ crc16_table[((fcs) ^ (c)) & 0xff]) +unsigned short AddCRC16Byte(unsigned short fcs, unsigned char byte) +{ + return CalcFCS(fcs, byte); +} +unsigned short AddCRC16(unsigned short fcs, void const *Buf, + unsigned long long len) +{ + unsigned char const *buf = (unsigned char const *)Buf; + while (len--) + fcs = CalcFCS(fcs, *buf++); + return fcs; +} diff --git a/apt-pkg/contrib/crc-16.h b/apt-pkg/contrib/crc-16.h new file mode 100644 index 0000000..3e07560 --- /dev/null +++ b/apt-pkg/contrib/crc-16.h @@ -0,0 +1,19 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + CRC16 - Compute a 16bit crc very quickly + + ##################################################################### */ + /*}}}*/ +#ifndef APTPKG_CRC16_H +#define APTPKG_CRC16_H + +#include <apt-pkg/macros.h> + +#define INIT_FCS 0xffff +unsigned short AddCRC16Byte(unsigned short fcs, unsigned char byte) APT_PURE; +unsigned short AddCRC16(unsigned short fcs, void const *buf, + unsigned long long len) APT_PURE; + +#endif diff --git a/apt-pkg/contrib/error.cc b/apt-pkg/contrib/error.cc new file mode 100644 index 0000000..3c397ea --- /dev/null +++ b/apt-pkg/contrib/error.cc @@ -0,0 +1,244 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Global Error Class - Global error mechanism + + We use a simple STL vector to store each error record. A PendingFlag + is kept which indicates when the vector contains a Sever error. + + This source is placed in the Public Domain, do with it what you will + It was originally written by Jason Gunthorpe. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/error.h> + +#include <algorithm> +#include <cstring> +#include <iostream> +#include <list> +#include <string> +#include <errno.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + + /*}}}*/ + +// Global Error Object /*{{{*/ +GlobalError *_GetErrorObj() +{ + static thread_local GlobalError Obj; + return &Obj; +} + /*}}}*/ +// GlobalError::GlobalError - Constructor /*{{{*/ +GlobalError::GlobalError() : PendingFlag(false) {} + /*}}}*/ +// GlobalError::FatalE, Errno, WarningE, NoticeE and DebugE - Add to the list/*{{{*/ +#define GEMessage(NAME, TYPE) \ +bool GlobalError::NAME (const char *Function, const char *Description,...) { \ + va_list args; \ + size_t msgSize = 400; \ + int const errsv = errno; \ + bool retry; \ + do { \ + va_start(args,Description); \ + retry = InsertErrno(TYPE, Function, Description, args, errsv, msgSize); \ + va_end(args); \ + } while (retry); \ + return false; \ +} +GEMessage(FatalE, FATAL) +GEMessage(Errno, ERROR) +GEMessage(WarningE, WARNING) +GEMessage(NoticeE, NOTICE) +GEMessage(DebugE, DEBUG) +#undef GEMessage + /*}}}*/ +// GlobalError::InsertErrno - Get part of the errortype string from errno/*{{{*/ +bool GlobalError::InsertErrno(MsgType const &type, const char *Function, + const char *Description,...) { + va_list args; + size_t msgSize = 400; + int const errsv = errno; + bool retry; + do { + va_start(args,Description); + retry = InsertErrno(type, Function, Description, args, errsv, msgSize); + va_end(args); + } while (retry); + return false; +} + /*}}}*/ +// GlobalError::InsertErrno - formats an error message with the errno /*{{{*/ +bool GlobalError::InsertErrno(MsgType type, const char* Function, + const char* Description, va_list &args, + int const errsv, size_t &msgSize) { + char* S = (char*) malloc(msgSize); + int const n = snprintf(S, msgSize, "%s - %s (%i: %s)", Description, + Function, errsv, strerror(errsv)); + if (n > -1 && ((unsigned int) n) < msgSize); + else { + if (n > -1) + msgSize = n + 1; + else + msgSize *= 2; + free(S); + return true; + } + + bool const geins = Insert(type, S, args, msgSize); + free(S); + return geins; +} + /*}}}*/ +// GlobalError::Fatal, Error, Warning, Notice and Debug - Add to the list/*{{{*/ +#define GEMessage(NAME, TYPE) \ +bool GlobalError::NAME (const char *Description,...) { \ + va_list args; \ + size_t msgSize = 400; \ + bool retry; \ + do { \ + va_start(args,Description); \ + retry = Insert(TYPE, Description, args, msgSize); \ + va_end(args); \ + } while (retry); \ + return false; \ +} +GEMessage(Fatal, FATAL) +GEMessage(Error, ERROR) +GEMessage(Warning, WARNING) +GEMessage(Notice, NOTICE) +GEMessage(Debug, DEBUG) +#undef GEMessage + /*}}}*/ +// GlobalError::Insert - Add a errotype message to the list /*{{{*/ +bool GlobalError::Insert(MsgType const &type, const char *Description,...) +{ + va_list args; + size_t msgSize = 400; + bool retry; + do { + va_start(args,Description); + retry = Insert(type, Description, args, msgSize); + va_end(args); + } while (retry); + return false; +} + /*}}}*/ +// GlobalError::Insert - Insert a new item at the end /*{{{*/ +bool GlobalError::Insert(MsgType type, const char* Description, + va_list &args, size_t &msgSize) { + char* S = (char*) malloc(msgSize); + int const n = vsnprintf(S, msgSize, Description, args); + if (n > -1 && ((unsigned int) n) < msgSize); + else { + if (n > -1) + msgSize = n + 1; + else + msgSize *= 2; + free(S); + return true; + } + + Item const m(S, type); + Messages.push_back(m); + + if (type == ERROR || type == FATAL) + PendingFlag = true; + + if (type == FATAL || type == DEBUG) + std::clog << m << std::endl; + + free(S); + return false; +} + /*}}}*/ +// GlobalError::PopMessage - Pulls a single message out /*{{{*/ +bool GlobalError::PopMessage(std::string &Text) { + if (Messages.empty() == true) + return false; + + Item const msg = Messages.front(); + Messages.pop_front(); + + bool const Ret = (msg.Type == ERROR || msg.Type == FATAL); + Text = msg.Text; + if (PendingFlag == false || Ret == false) + return Ret; + + // check if another error message is pending + for (std::list<Item>::const_iterator m = Messages.begin(); + m != Messages.end(); ++m) + if (m->Type == ERROR || m->Type == FATAL) + return Ret; + + PendingFlag = false; + return Ret; +} + /*}}}*/ +// GlobalError::DumpErrors - Dump all of the errors/warns to cerr /*{{{*/ +void GlobalError::DumpErrors(std::ostream &out, MsgType const &threshold, + bool const &mergeStack) { + if (mergeStack == true) + for (std::list<MsgStack>::const_reverse_iterator s = Stacks.rbegin(); + s != Stacks.rend(); ++s) + std::copy(s->Messages.begin(), s->Messages.end(), std::front_inserter(Messages)); + + std::for_each(Messages.begin(), Messages.end(), [&threshold, &out](Item const &m) { + if (m.Type >= threshold) + out << m << std::endl; + }); + + Discard(); +} + /*}}}*/ +// GlobalError::Discard - Discard /*{{{*/ +void GlobalError::Discard() { + Messages.clear(); + PendingFlag = false; +} + /*}}}*/ +// GlobalError::empty - does our error list include anything? /*{{{*/ +bool GlobalError::empty(MsgType const &threshold) const { + if (PendingFlag == true) + return false; + + if (Messages.empty() == true) + return true; + + return std::find_if(Messages.begin(), Messages.end(), [&threshold](Item const &m) { + return m.Type >= threshold; + }) == Messages.end(); +} + /*}}}*/ +// GlobalError::PushToStack /*{{{*/ +void GlobalError::PushToStack() { + Stacks.emplace_back(Messages, PendingFlag); + Discard(); +} + /*}}}*/ +// GlobalError::RevertToStack /*{{{*/ +void GlobalError::RevertToStack() { + Discard(); + MsgStack pack = Stacks.back(); + Messages = pack.Messages; + PendingFlag = pack.PendingFlag; + Stacks.pop_back(); +} + /*}}}*/ +// GlobalError::MergeWithStack /*{{{*/ +void GlobalError::MergeWithStack() { + MsgStack pack = Stacks.back(); + Messages.splice(Messages.begin(), pack.Messages); + PendingFlag = PendingFlag || pack.PendingFlag; + Stacks.pop_back(); +} + /*}}}*/ diff --git a/apt-pkg/contrib/error.h b/apt-pkg/contrib/error.h new file mode 100644 index 0000000..d0f4507 --- /dev/null +++ b/apt-pkg/contrib/error.h @@ -0,0 +1,365 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Global Error Class - Global error mechanism + + This class has a single global instance. When a function needs to + generate an error condition, such as a read error, it calls a member + in this class to add the error to a stack of errors. + + By using a stack the problem with a scheme like errno is removed and + it allows a very detailed account of what went wrong to be transmitted + to the UI for display. (Errno has problems because each function sets + errno to 0 if it didn't have an error thus eraseing erno in the process + of cleanup) + + Several predefined error generators are provided to handle common + things like errno. The general idea is that all methods return a bool. + If the bool is true then things are OK, if it is false then things + should start being undone and the stack should unwind under program + control. + + A Warning should not force the return of false. Things did not fail, but + they might have had unexpected problems. Errors are stored in a FIFO + so Pop will return the first item.. + + I have some thoughts about extending this into a more general UI<-> + Engine interface, ie allowing the Engine to say 'The disk is full' in + a dialog that says 'Panic' and 'Retry'.. The error generator functions + like errno, Warning and Error return false always so this is normal: + if (open(..)) + return _error->Errno(..); + + This source is placed in the Public Domain, do with it what you will + It was originally written by Jason Gunthorpe. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_ERROR_H +#define PKGLIB_ERROR_H + +#include <apt-pkg/macros.h> + +#include <iostream> +#include <list> +#include <string> + +#include <stdarg.h> +#include <stddef.h> + +class GlobalError /*{{{*/ +{ +public: /*{{{*/ + /** \brief a message can have one of following severity */ + enum MsgType { + /** \brief Message will be printed instantly as it is likely that + this error will lead to a complete crash */ + FATAL = 40, + /** \brief An error does hinder the correct execution and should be corrected */ + ERROR = 30, + /** \brief indicates problem that can lead to errors later on */ + WARNING = 20, + /** \brief deprecation warnings, old fallback behavior, … */ + NOTICE = 10, + /** \brief for developers only in areas it is hard to print something directly */ + DEBUG = 0 + }; + + /** \brief add a fatal error message with errno to the list + * + * \param Function name of the function generating the error + * \param Description format string for the error message + * + * \return \b false + */ + bool FatalE(const char *Function,const char *Description,...) APT_PRINTF(3) APT_COLD; + + /** \brief add an Error message with errno to the list + * + * \param Function name of the function generating the error + * \param Description format string for the error message + * + * \return \b false + */ + bool Errno(const char *Function,const char *Description,...) APT_PRINTF(3) APT_COLD; + + /** \brief add a warning message with errno to the list + * + * A warning should be considered less severe than an error and + * may be ignored by the client. + * + * \param Function Name of the function generates the warning. + * \param Description Format string for the warning message. + * + * \return \b false + */ + bool WarningE(const char *Function,const char *Description,...) APT_PRINTF(3) APT_COLD; + + /** \brief add a notice message with errno to the list + * + * \param Function name of the function generating the error + * \param Description format string for the error message + * + * \return \b false + */ + bool NoticeE(const char *Function,const char *Description,...) APT_PRINTF(3) APT_COLD; + + /** \brief add a debug message with errno to the list + * + * \param Function name of the function generating the error + * \param Description format string for the error message + * + * \return \b false + */ + bool DebugE(const char *Function,const char *Description,...) APT_PRINTF(3) APT_COLD; + + /** \brief adds an errno message with the given type + * + * \param type of the error message + * \param Function which failed + * \param Description of the error + */ + bool InsertErrno(MsgType const &type, const char* Function, + const char* Description,...) APT_PRINTF(4) APT_COLD; + + /** \brief adds an errno message with the given type + * + * args needs to be initialized with va_start and terminated + * with va_end by the caller. msgSize is also an out-parameter + * in case the msgSize was not enough to store the complete message. + * + * \param type of the error message + * \param Function which failed + * \param Description is the format string for args + * \param args list from a printf-like function + * \param errsv is the errno the error is for + * \param msgSize is the size of the char[] used to store message + * \return true if the message was added, false if not - the caller + * should call this method again in that case + */ + bool InsertErrno(MsgType type, const char* Function, + const char* Description, va_list &args, + int const errsv, size_t &msgSize) APT_COLD; + + /** \brief add an fatal error message to the list + * + * Most of the stuff we consider as "error" is also "fatal" for + * the user as the application will not have the expected result, + * but a fatal message here means that it gets printed directly + * to stderr in addition to adding it to the list as the error + * leads sometimes to crashes and a maybe duplicated message + * is better than "Segfault" as the only displayed text + * + * \param Description Format string for the fatal error message. + * + * \return \b false + */ + bool Fatal(const char *Description,...) APT_PRINTF(2) APT_COLD; + + /** \brief add an Error message to the list + * + * \param Description Format string for the error message. + * + * \return \b false + */ + bool Error(const char *Description,...) APT_PRINTF(2) APT_COLD; + + /** \brief add a warning message to the list + * + * A warning should be considered less severe than an error and + * may be ignored by the client. + * + * \param Description Format string for the message + * + * \return \b false + */ + bool Warning(const char *Description,...) APT_PRINTF(2) APT_COLD; + + /** \brief add a notice message to the list + * + * A notice should be considered less severe than an error or a + * warning and can be ignored by the client without further problems + * for some times, but he should consider fixing the problem. + * This error type can be used for e.g. deprecation warnings of options. + * + * \param Description Format string for the message + * + * \return \b false + */ + bool Notice(const char *Description,...) APT_PRINTF(2) APT_COLD; + + /** \brief add a debug message to the list + * + * \param Description Format string for the message + * + * \return \b false + */ + bool Debug(const char *Description,...) APT_PRINTF(2) APT_COLD; + + /** \brief adds an error message with the given type + * + * \param type of the error message + * \param Description of the error + */ + bool Insert(MsgType const &type, const char* Description,...) APT_PRINTF(3) APT_COLD; + + /** \brief adds an error message with the given type + * + * args needs to be initialized with va_start and terminated + * with va_end by the caller. msgSize is also an out-parameter + * in case the msgSize was not enough to store the complete message. + * + * \param type of the error message + * \param Description is the format string for args + * \param args list from a printf-like function + * \param msgSize is the size of the char[] used to store message + * \return true if the message was added, false if not - the caller + * should call this method again in that case + */ + bool Insert(MsgType type, const char* Description, + va_list &args, size_t &msgSize) APT_COLD; + + /** \brief is an error in the list? + * + * \return \b true if an error is included in the list, \b false otherwise + */ + inline bool PendingError() const APT_PURE {return PendingFlag;}; + + /** \brief is the list empty? + * + * Can be used to check if the current stack level doesn't include + * anything equal or more severe than a given threshold, defaulting + * to warning level for historic reasons. + * + * \param threshold minimum level considered + * + * \return \b true if the list is empty, \b false otherwise + */ + bool empty(MsgType const &threshold = WARNING) const APT_PURE; + + /** \brief returns and removes the first (or last) message in the list + * + * \param[out] Text message of the first/last item + * + * \return \b true if the message was an error, \b false otherwise + */ + bool PopMessage(std::string &Text); + + /** \brief clears the list of messages */ + void Discard(); + + /** \brief outputs the list of messages to the given stream + * + * Note that all messages are discarded, even undisplayed ones. + * + * \param[out] out output stream to write the messages in + * \param threshold minimum level considered + * \param mergeStack if true recursively dumps the entire stack + */ + void DumpErrors(std::ostream &out, MsgType const &threshold = WARNING, + bool const &mergeStack = true); + + /** \brief dumps the list of messages to std::cerr + * + * Note that all messages are discarded, also the notices + * displayed or not. + * + * \param threshold minimum level printed + */ + void inline DumpErrors(MsgType const &threshold) { + DumpErrors(std::cerr, threshold); + } + + // mvo: we do this instead of using a default parameter in the + // previous declaration to avoid a (subtle) API break for + // e.g. sigc++ and mem_fun0 + /** \brief dumps the messages of type WARNING or higher to std::cerr + * + * Note that all messages are discarded, displayed or not. + * + */ + void inline DumpErrors() { + DumpErrors(WARNING); + } + + /** \brief put the current Messages into the stack + * + * All "old" messages will be pushed into a stack to + * them later back, but for now the Message query will be + * empty and performs as no messages were present before. + * + * The stack can be as deep as you want - all stack operations + * will only operate on the last element in the stack. + */ + void PushToStack(); + + /** \brief throw away all current messages */ + void RevertToStack(); + + /** \brief merge current and stack together */ + void MergeWithStack(); + + /** \brief return the deep of the stack */ + size_t StackCount() const APT_PURE { + return Stacks.size(); + } + + GlobalError(); + /*}}}*/ +private: /*{{{*/ + struct Item { + std::string Text; + MsgType Type; + + Item(char const *Text, MsgType const &Type) : + Text(Text), Type(Type) {}; + + APT_HIDDEN friend std::ostream& operator<< (std::ostream &out, Item i) { + switch(i.Type) { + case FATAL: + case ERROR: out << 'E'; break; + case WARNING: out << 'W'; break; + case NOTICE: out << 'N'; break; + case DEBUG: out << 'D'; break; + } + out << ": "; + std::string::size_type line_start = 0; + std::string::size_type line_end; + while ((line_end = i.Text.find_first_of("\n\r", line_start)) != std::string::npos) { + if (line_start != 0) + out << std::endl << " "; + out << i.Text.substr(line_start, line_end - line_start); + line_start = i.Text.find_first_not_of("\n\r", line_end + 1); + if (line_start == std::string::npos) + break; + } + if (line_start == 0) + out << i.Text; + else if (line_start != std::string::npos) + out << std::endl << " " << i.Text.substr(line_start); + return out; + } + }; + + std::list<Item> Messages; + bool PendingFlag; + + struct MsgStack { + std::list<Item> Messages; + bool const PendingFlag; + + MsgStack(std::list<Item> const &Messages, bool const &Pending) : + Messages(Messages), PendingFlag(Pending) {}; + }; + + std::list<MsgStack> Stacks; + /*}}}*/ +}; + /*}}}*/ + +// The 'extra-ansi' syntax is used to help with collisions. +GlobalError *_GetErrorObj(); +#define _error _GetErrorObj() + +#endif diff --git a/apt-pkg/contrib/fileutl.cc b/apt-pkg/contrib/fileutl.cc new file mode 100644 index 0000000..4f12349 --- /dev/null +++ b/apt-pkg/contrib/fileutl.cc @@ -0,0 +1,3443 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + File Utilities + + CopyFile - Buffered copy of a single file + GetLock - dpkg compatible lock file manipulation (fcntl) + + Most of this source is placed in the Public Domain, do with it what + you will + It was originally written by Jason Gunthorpe <jgg@debian.org>. + FileFd gzip support added by Martin Pitt <martin.pitt@canonical.com> + + The exception is RunScripts() it is under the GPLv2 + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgsystem.h> +#include <apt-pkg/sptr.h> +#include <apt-pkg/strutl.h> + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <iostream> +#include <string> +#include <vector> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <glob.h> +#include <grp.h> +#include <pwd.h> +#include <signal.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include <algorithm> +#include <memory> +#include <set> + +#ifdef HAVE_ZLIB +#include <zlib.h> +#endif +#ifdef HAVE_BZ2 +#include <bzlib.h> +#endif +#ifdef HAVE_LZMA +#include <lzma.h> +#endif +#ifdef HAVE_LZ4 +#include <lz4frame.h> +#endif +#ifdef HAVE_ZSTD +#include <zstd.h> +#endif +#ifdef HAVE_SYSTEMD +#include <systemd/sd-bus.h> +#endif +#include <endian.h> +#include <stdint.h> + +#if __gnu_linux__ +#include <sys/prctl.h> +#endif + +#include <apti18n.h> + /*}}}*/ + +using namespace std; + +/* Should be a multiple of the common page size (4096) */ +static constexpr unsigned long long APT_BUFFER_SIZE = 64 * 1024; + +// RunScripts - Run a set of scripts from a configuration subtree /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool RunScripts(const char *Cnf) +{ + Configuration::Item const *Opts = _config->Tree(Cnf); + if (Opts == 0 || Opts->Child == 0) + return true; + Opts = Opts->Child; + + // Fork for running the system calls + pid_t Child = ExecFork(); + + // This is the child + if (Child == 0) + { + if (_system != nullptr && _system->IsLocked() == true && (stringcasecmp(Cnf, "dpkg::post-invoke") == 0 || stringcasecmp(Cnf, "dpkg::pre-invoke") == 0)) + setenv("DPKG_FRONTEND_LOCKED", "true", 1); + if (_config->FindDir("DPkg::Chroot-Directory","/") != "/") + { + std::cerr << "Chrooting into " + << _config->FindDir("DPkg::Chroot-Directory") + << std::endl; + if (chroot(_config->FindDir("DPkg::Chroot-Directory","/").c_str()) != 0) + _exit(100); + } + + if (chdir("/tmp/") != 0) + _exit(100); + + 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: '" + << Opts->Value << "'" << std::endl; + + if (system(Opts->Value.c_str()) != 0) + _exit(100+Count); + } + _exit(0); + } + + // Wait for the child + int Status = 0; + while (waitpid(Child,&Status,0) != Child) + { + if (errno == EINTR) + continue; + return _error->Errno("waitpid","Couldn't wait for subprocess"); + } + + // Restore sig int/quit + signal(SIGQUIT,SIG_DFL); + signal(SIGINT,SIG_DFL); + + // Check for an error code. + if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0) + { + unsigned int Count = WEXITSTATUS(Status); + if (Count > 100) + { + Count -= 100; + for (; Opts != 0 && Count != 1; Opts = Opts->Next, Count--); + _error->Error("Problem executing scripts %s '%s'",Cnf,Opts->Value.c_str()); + } + + return _error->Error("Sub-process returned an error code"); + } + + return true; +} + /*}}}*/ + +// CopyFile - Buffered copy of a file /*{{{*/ +// --------------------------------------------------------------------- +/* The caller is expected to set things so that failure causes erasure */ +bool CopyFile(FileFd &From,FileFd &To) +{ + if (From.IsOpen() == false || To.IsOpen() == false || + From.Failed() == true || To.Failed() == true) + return false; + + // Buffered copy between fds + constexpr size_t BufSize = APT_BUFFER_SIZE; + std::unique_ptr<unsigned char[]> Buf(new unsigned char[BufSize]); + unsigned long long ToRead = 0; + do { + if (From.Read(Buf.get(),BufSize, &ToRead) == false || + To.Write(Buf.get(),ToRead) == false) + return false; + } while (ToRead != 0); + + return true; +} + /*}}}*/ +bool RemoveFileAt(char const * const Function, int const dirfd, std::string const &FileName)/*{{{*/ +{ + if (FileName == "/dev/null") + return true; + errno = 0; + if (unlinkat(dirfd, FileName.c_str(), 0) != 0) + { + if (errno == ENOENT) + return true; + + return _error->WarningE(Function,_("Problem unlinking the file %s"), FileName.c_str()); + } + return true; +} + /*}}}*/ +bool RemoveFile(char const * const Function, std::string const &FileName)/*{{{*/ +{ + if (FileName == "/dev/null") + return true; + errno = 0; + if (unlink(FileName.c_str()) != 0) + { + if (errno == ENOENT) + return true; + + return _error->WarningE(Function,_("Problem unlinking the file %s"), FileName.c_str()); + } + return true; +} + /*}}}*/ +// GetLock - Gets a lock file /*{{{*/ +// --------------------------------------------------------------------- +/* This will create an empty file of the given name and lock it. Once this + is done all other calls to GetLock in any other process will fail with + -1. The return result is the fd of the file, the call should call + close at some time. */ +int GetLock(string File,bool Errors) +{ + // GetLock() is used in aptitude on directories with public-write access + // Use O_NOFOLLOW here to prevent symlink traversal attacks + int FD = open(File.c_str(),O_RDWR | O_CREAT | O_NOFOLLOW,0640); + if (FD < 0) + { + // Read only .. can't have locking problems there. + if (errno == EROFS) + { + _error->Warning(_("Not using locking for read only lock file %s"),File.c_str()); + return dup(0); // Need something for the caller to close + } + + if (Errors == true) + _error->Errno("open",_("Could not open lock file %s"),File.c_str()); + + // Feh.. We do this to distinguish the lock vs open case.. + errno = EPERM; + return -1; + } + SetCloseExec(FD,true); + + // Acquire a write lock + struct flock fl; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + if (fcntl(FD,F_SETLK,&fl) == -1) + { + // always close to not leak resources + int Tmp = errno; + close(FD); + errno = Tmp; + + if (errno == ENOLCK) + { + _error->Warning(_("Not using locking for nfs mounted lock file %s"),File.c_str()); + return dup(0); // Need something for the caller to close + } + + if (Errors == true) + _error->Errno("open",_("Could not get lock %s"),File.c_str()); + + return -1; + } + + return FD; +} + /*}}}*/ +// FileExists - Check if a file exists /*{{{*/ +// --------------------------------------------------------------------- +/* Beware: Directories are also files! */ +bool FileExists(string File) +{ + struct stat Buf; + if (stat(File.c_str(),&Buf) != 0) + return false; + return true; +} + /*}}}*/ +// RealFileExists - Check if a file exists and if it is really a file /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool RealFileExists(string File) +{ + struct stat Buf; + if (stat(File.c_str(),&Buf) != 0) + return false; + return ((Buf.st_mode & S_IFREG) != 0); +} + /*}}}*/ +// DirectoryExists - Check if a directory exists and is really one /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool DirectoryExists(string const &Path) +{ + struct stat Buf; + if (stat(Path.c_str(),&Buf) != 0) + return false; + return ((Buf.st_mode & S_IFDIR) != 0); +} + /*}}}*/ +// CreateDirectory - poor man's mkdir -p guarded by a parent directory /*{{{*/ +// --------------------------------------------------------------------- +/* This method will create all directories needed for path in good old + mkdir -p style but refuses to do this if Parent is not a prefix of + this Path. Example: /var/cache/ and /var/cache/apt/archives are given, + so it will create apt/archives if /var/cache exists - on the other + hand if the parent is /var/lib the creation will fail as this path + is not a parent of the path to be generated. */ +bool CreateDirectory(string const &Parent, string const &Path) +{ + if (Parent.empty() == true || Path.empty() == true) + return false; + + if (DirectoryExists(Path) == true) + return true; + + if (DirectoryExists(Parent) == false) + return false; + + // we are not going to create directories "into the blue" + if (Path.compare(0, Parent.length(), Parent) != 0) + return false; + + vector<string> const dirs = VectorizeString(Path.substr(Parent.size()), '/'); + string progress = Parent; + for (vector<string>::const_iterator d = dirs.begin(); d != dirs.end(); ++d) + { + if (d->empty() == true) + continue; + + progress.append("/").append(*d); + if (DirectoryExists(progress) == true) + continue; + + if (mkdir(progress.c_str(), 0755) != 0) + return false; + } + return true; +} + /*}}}*/ +// CreateAPTDirectoryIfNeeded - ensure that the given directory exists /*{{{*/ +// --------------------------------------------------------------------- +/* a small wrapper around CreateDirectory to check if it exists and to + remove the trailing "/apt/" from the parent directory if needed */ +bool CreateAPTDirectoryIfNeeded(string const &Parent, string const &Path) +{ + if (DirectoryExists(Path) == true) + return true; + + size_t const len = Parent.size(); + if (len > 5 && Parent.find("/apt/", len - 6, 5) == len - 5) + { + if (CreateDirectory(Parent.substr(0,len-5), Path) == true) + return true; + } + else if (CreateDirectory(Parent, Path) == true) + return true; + + return false; +} + /*}}}*/ +// GetListOfFilesInDir - returns a vector of files in the given dir /*{{{*/ +// --------------------------------------------------------------------- +/* If an extension is given only files with this extension are included + in the returned vector, otherwise every "normal" file is included. */ +std::vector<string> GetListOfFilesInDir(string const &Dir, string const &Ext, + bool const &SortList, bool const &AllowNoExt) +{ + std::vector<string> ext; + ext.reserve(2); + if (Ext.empty() == false) + ext.push_back(Ext); + if (AllowNoExt == true && ext.empty() == false) + ext.push_back(""); + return GetListOfFilesInDir(Dir, ext, SortList); +} +std::vector<string> GetListOfFilesInDir(string const &Dir, std::vector<string> const &Ext, + bool const &SortList) +{ + // Attention debuggers: need to be set with the environment config file! + bool const Debug = _config->FindB("Debug::GetListOfFilesInDir", false); + if (Debug == true) + { + std::clog << "Accept in " << Dir << " only files with the following " << Ext.size() << " extensions:" << std::endl; + if (Ext.empty() == true) + std::clog << "\tNO extension" << std::endl; + else + for (std::vector<string>::const_iterator e = Ext.begin(); + e != Ext.end(); ++e) + std::clog << '\t' << (e->empty() == true ? "NO" : *e) << " extension" << std::endl; + } + + std::vector<string> List; + + if (DirectoryExists(Dir) == false) + { + _error->Error(_("List of files can't be created as '%s' is not a directory"), Dir.c_str()); + return List; + } + + Configuration::MatchAgainstConfig SilentIgnore("Dir::Ignore-Files-Silently"); + DIR *D = opendir(Dir.c_str()); + if (D == 0) + { + if (errno == EACCES) + _error->WarningE("opendir", _("Unable to read %s"), Dir.c_str()); + else + _error->Errno("opendir", _("Unable to read %s"), Dir.c_str()); + return List; + } + + for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D)) + { + // skip "hidden" files + if (Ent->d_name[0] == '.') + continue; + + // Make sure it is a file and not something else + string const File = flCombine(Dir,Ent->d_name); +#ifdef _DIRENT_HAVE_D_TYPE + if (Ent->d_type != DT_REG) +#endif + { + if (RealFileExists(File) == false) + { + // do not show ignoration warnings for directories + if ( +#ifdef _DIRENT_HAVE_D_TYPE + Ent->d_type == DT_DIR || +#endif + DirectoryExists(File) == true) + continue; + if (SilentIgnore.Match(Ent->d_name) == false) + _error->Notice(_("Ignoring '%s' in directory '%s' as it is not a regular file"), Ent->d_name, Dir.c_str()); + continue; + } + } + + // check for accepted extension: + // no extension given -> periods are bad as hell! + // extensions given -> "" extension allows no extension + if (Ext.empty() == false) + { + string d_ext = flExtension(Ent->d_name); + if (d_ext == Ent->d_name) // no extension + { + if (std::find(Ext.begin(), Ext.end(), "") == Ext.end()) + { + if (Debug == true) + std::clog << "Bad file: " << Ent->d_name << " → no extension" << std::endl; + if (SilentIgnore.Match(Ent->d_name) == false) + _error->Notice(_("Ignoring file '%s' in directory '%s' as it has no filename extension"), Ent->d_name, Dir.c_str()); + continue; + } + } + else if (std::find(Ext.begin(), Ext.end(), d_ext) == Ext.end()) + { + if (Debug == true) + std::clog << "Bad file: " << Ent->d_name << " → bad extension »" << flExtension(Ent->d_name) << "«" << std::endl; + if (SilentIgnore.Match(Ent->d_name) == false) + _error->Notice(_("Ignoring file '%s' in directory '%s' as it has an invalid filename extension"), Ent->d_name, Dir.c_str()); + continue; + } + } + + // Skip bad filenames ala run-parts + const char *C = Ent->d_name; + for (; *C != 0; ++C) + if (isalpha(*C) == 0 && isdigit(*C) == 0 + && *C != '_' && *C != '-' && *C != ':') { + // no required extension -> dot is a bad character + if (*C == '.' && Ext.empty() == false) + continue; + break; + } + + // we don't reach the end of the name -> bad character included + if (*C != 0) + { + if (Debug == true) + std::clog << "Bad file: " << Ent->d_name << " → bad character »" + << *C << "« in filename (period allowed: " << (Ext.empty() ? "no" : "yes") << ")" << std::endl; + continue; + } + + // skip filenames which end with a period. These are never valid + if (*(C - 1) == '.') + { + if (Debug == true) + std::clog << "Bad file: " << Ent->d_name << " → Period as last character" << std::endl; + continue; + } + + if (Debug == true) + std::clog << "Accept file: " << Ent->d_name << " in " << Dir << std::endl; + List.push_back(File); + } + closedir(D); + + if (SortList == true) + std::sort(List.begin(),List.end()); + return List; +} +std::vector<string> GetListOfFilesInDir(string const &Dir, bool SortList) +{ + bool const Debug = _config->FindB("Debug::GetListOfFilesInDir", false); + if (Debug == true) + std::clog << "Accept in " << Dir << " all regular files" << std::endl; + + std::vector<string> List; + + if (DirectoryExists(Dir) == false) + { + _error->Error(_("List of files can't be created as '%s' is not a directory"), Dir.c_str()); + return List; + } + + DIR *D = opendir(Dir.c_str()); + if (D == 0) + { + _error->Errno("opendir",_("Unable to read %s"),Dir.c_str()); + return List; + } + + for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D)) + { + // skip "hidden" files + if (Ent->d_name[0] == '.') + continue; + + // Make sure it is a file and not something else + string const File = flCombine(Dir,Ent->d_name); +#ifdef _DIRENT_HAVE_D_TYPE + if (Ent->d_type != DT_REG) +#endif + { + if (RealFileExists(File) == false) + { + if (Debug == true) + std::clog << "Bad file: " << Ent->d_name << " → it is not a real file" << std::endl; + continue; + } + } + + // Skip bad filenames ala run-parts + const char *C = Ent->d_name; + for (; *C != 0; ++C) + if (isalpha(*C) == 0 && isdigit(*C) == 0 + && *C != '_' && *C != '-' && *C != '.') + break; + + // we don't reach the end of the name -> bad character included + if (*C != 0) + { + if (Debug == true) + std::clog << "Bad file: " << Ent->d_name << " → bad character »" << *C << "« in filename" << std::endl; + continue; + } + + // skip filenames which end with a period. These are never valid + if (*(C - 1) == '.') + { + if (Debug == true) + std::clog << "Bad file: " << Ent->d_name << " → Period as last character" << std::endl; + continue; + } + + if (Debug == true) + std::clog << "Accept file: " << Ent->d_name << " in " << Dir << std::endl; + List.push_back(File); + } + closedir(D); + + if (SortList == true) + std::sort(List.begin(),List.end()); + return List; +} + /*}}}*/ +// SafeGetCWD - This is a safer getcwd that returns a dynamic string /*{{{*/ +// --------------------------------------------------------------------- +/* We return / on failure. */ +string SafeGetCWD() +{ + // Stash the current dir. + char S[300]; + S[0] = 0; + if (getcwd(S,sizeof(S)-2) == 0) + return "/"; + unsigned int Len = strlen(S); + S[Len] = '/'; + S[Len+1] = 0; + return S; +} + /*}}}*/ +// GetModificationTime - Get the mtime of the given file or -1 on error /*{{{*/ +// --------------------------------------------------------------------- +/* We return / on failure. */ +time_t GetModificationTime(string const &Path) +{ + struct stat St; + if (stat(Path.c_str(), &St) < 0) + return -1; + return St.st_mtime; +} + /*}}}*/ +// flNotDir - Strip the directory from the filename /*{{{*/ +// --------------------------------------------------------------------- +/* */ +string flNotDir(string File) +{ + string::size_type Res = File.rfind('/'); + if (Res == string::npos) + return File; + Res++; + return string(File,Res,Res - File.length()); +} + /*}}}*/ +// flNotFile - Strip the file from the directory name /*{{{*/ +// --------------------------------------------------------------------- +/* Result ends in a / */ +string flNotFile(string File) +{ + string::size_type Res = File.rfind('/'); + if (Res == string::npos) + return "./"; + Res++; + return string(File,0,Res); +} + /*}}}*/ +// flExtension - Return the extension for the file /*{{{*/ +// --------------------------------------------------------------------- +/* */ +string flExtension(string File) +{ + string::size_type Res = File.rfind('.'); + if (Res == string::npos) + return File; + Res++; + return string(File,Res,Res - File.length()); +} + /*}}}*/ +// flNoLink - If file is a symlink then deref it /*{{{*/ +// --------------------------------------------------------------------- +/* If the name is not a link then the returned path is the input. */ +string flNoLink(string File) +{ + struct stat St; + if (lstat(File.c_str(),&St) != 0 || S_ISLNK(St.st_mode) == 0) + return File; + if (stat(File.c_str(),&St) != 0) + return File; + + /* Loop resolving the link. There is no need to limit the number of + loops because the stat call above ensures that the symlink is not + circular */ + char Buffer[1024]; + string NFile = File; + while (1) + { + // Read the link + ssize_t Res; + if ((Res = readlink(NFile.c_str(),Buffer,sizeof(Buffer))) <= 0 || + (size_t)Res >= sizeof(Buffer)) + return File; + + // Append or replace the previous path + Buffer[Res] = 0; + if (Buffer[0] == '/') + NFile = Buffer; + else + NFile = flNotFile(NFile) + Buffer; + + // See if we are done + if (lstat(NFile.c_str(),&St) != 0) + return File; + if (S_ISLNK(St.st_mode) == 0) + return NFile; + } +} + /*}}}*/ +// flCombine - Combine a file and a directory /*{{{*/ +// --------------------------------------------------------------------- +/* If the file is an absolute path then it is just returned, otherwise + the directory is pre-pended to it. */ +string flCombine(string Dir,string File) +{ + if (File.empty() == true) + return string(); + + if (File[0] == '/' || Dir.empty() == true) + return File; + if (File.length() >= 2 && File[0] == '.' && File[1] == '/') + return File; + if (Dir[Dir.length()-1] == '/') + return Dir + File; + return Dir + '/' + File; +} + /*}}}*/ +// flAbsPath - Return the absolute path of the filename /*{{{*/ +// --------------------------------------------------------------------- +/* */ +string flAbsPath(string File) +{ + char *p = realpath(File.c_str(), NULL); + if (p == NULL) + { + _error->Errno("realpath", "flAbsPath on %s failed", File.c_str()); + return ""; + } + std::string AbsPath(p); + free(p); + return AbsPath; +} + /*}}}*/ +std::string flNormalize(std::string file) /*{{{*/ +{ + if (file.empty()) + return file; + // do some normalisation by removing // and /./ from the path + size_t found = string::npos; + while ((found = file.find("/./")) != string::npos) + file.replace(found, 3, "/"); + while ((found = file.find("//")) != string::npos) + file.replace(found, 2, "/"); + + if (APT::String::Startswith(file, "/dev/null")) + { + file.erase(strlen("/dev/null")); + return file; + } + return file; +} + /*}}}*/ +// SetCloseExec - Set the close on exec flag /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void SetCloseExec(int Fd,bool Close) +{ + if (fcntl(Fd,F_SETFD,(Close == false)?0:FD_CLOEXEC) != 0) + { + cerr << "FATAL -> Could not set close on exec " << strerror(errno) << endl; + exit(100); + } +} + /*}}}*/ +// SetNonBlock - Set the nonblocking flag /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void SetNonBlock(int Fd,bool Block) +{ + int Flags = fcntl(Fd,F_GETFL) & (~O_NONBLOCK); + if (fcntl(Fd,F_SETFL,Flags | ((Block == false)?0:O_NONBLOCK)) != 0) + { + cerr << "FATAL -> Could not set non-blocking flag " << strerror(errno) << endl; + exit(100); + } +} + /*}}}*/ +// WaitFd - Wait for a FD to become readable /*{{{*/ +// --------------------------------------------------------------------- +/* This waits for a FD to become readable using select. It is useful for + applications making use of non-blocking sockets. The timeout is + in seconds. */ +bool WaitFd(int Fd,bool write,unsigned long timeout) +{ + fd_set Set; + struct timeval tv; + FD_ZERO(&Set); + FD_SET(Fd,&Set); + tv.tv_sec = timeout; + tv.tv_usec = 0; + if (write == true) + { + int Res; + do + { + Res = select(Fd+1,0,&Set,0,(timeout != 0?&tv:0)); + } + while (Res < 0 && errno == EINTR); + + if (Res <= 0) + return false; + } + else + { + int Res; + do + { + Res = select(Fd+1,&Set,0,0,(timeout != 0?&tv:0)); + } + while (Res < 0 && errno == EINTR); + + if (Res <= 0) + return false; + } + + return true; +} + /*}}}*/ +// MergeKeepFdsFromConfiguration - Merge APT::Keep-Fds configuration /*{{{*/ +// --------------------------------------------------------------------- +/* This is used to merge the APT::Keep-Fds with the provided KeepFDs + * set. + */ +void MergeKeepFdsFromConfiguration(std::set<int> &KeepFDs) +{ + Configuration::Item const *Opts = _config->Tree("APT::Keep-Fds"); + if (Opts != 0 && Opts->Child != 0) + { + Opts = Opts->Child; + for (; Opts != 0; Opts = Opts->Next) + { + if (Opts->Value.empty() == true) + continue; + int fd = atoi(Opts->Value.c_str()); + KeepFDs.insert(fd); + } + } +} + /*}}}*/ +// ExecFork - Magical fork that sanitizes the context before execing /*{{{*/ +// --------------------------------------------------------------------- +/* This is used if you want to cleanse the environment for the forked + child, it fixes up the important signals and nukes all of the fds, + otherwise acts like normal fork. */ +pid_t ExecFork() +{ + set<int> KeepFDs; + // we need to merge the Keep-Fds as external tools like + // debconf-apt-progress use it + MergeKeepFdsFromConfiguration(KeepFDs); + return ExecFork(KeepFDs); +} + +pid_t ExecFork(std::set<int> KeepFDs) +{ + // Fork off the process + pid_t Process = fork(); + if (Process < 0) + { + cerr << "FATAL -> Failed to fork." << endl; + exit(100); + } + + // Spawn the subprocess + if (Process == 0) + { + // Setup the signals + signal(SIGPIPE,SIG_DFL); + signal(SIGQUIT,SIG_DFL); + signal(SIGINT,SIG_DFL); + signal(SIGWINCH,SIG_DFL); + signal(SIGCONT,SIG_DFL); + signal(SIGTSTP,SIG_DFL); + + DIR *dir = opendir("/proc/self/fd"); + if (dir != NULL) + { + struct dirent *ent; + while ((ent = readdir(dir))) + { + int fd = atoi(ent->d_name); + // If fd > 0, it was a fd number and not . or .. + if (fd >= 3 && KeepFDs.find(fd) == KeepFDs.end()) + fcntl(fd,F_SETFD,FD_CLOEXEC); + } + closedir(dir); + } else { + long ScOpenMax = sysconf(_SC_OPEN_MAX); + // Close all of our FDs - just in case + for (int K = 3; K != ScOpenMax; K++) + { + if(KeepFDs.find(K) == KeepFDs.end()) + fcntl(K,F_SETFD,FD_CLOEXEC); + } + } + } + + return Process; +} + /*}}}*/ +// ExecWait - Fancy waitpid /*{{{*/ +// --------------------------------------------------------------------- +/* Waits for the given sub process. If Reap is set then no errors are + generated. Otherwise a failed subprocess will generate a proper descriptive + message */ +bool ExecWait(pid_t Pid,const char *Name,bool Reap) +{ + if (Pid <= 1) + return true; + + // Wait and collect the error code + int Status; + while (waitpid(Pid,&Status,0) != Pid) + { + if (errno == EINTR) + continue; + + if (Reap == true) + return false; + + return _error->Error(_("Waited for %s but it wasn't there"),Name); + } + + + // Check for an error code. + if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0) + { + if (Reap == true) + return false; + if (WIFSIGNALED(Status) != 0) + { + if( WTERMSIG(Status) == SIGSEGV) + return _error->Error(_("Sub-process %s received a segmentation fault."),Name); + else + return _error->Error(_("Sub-process %s received signal %u."),Name, WTERMSIG(Status)); + } + + if (WIFEXITED(Status) != 0) + return _error->Error(_("Sub-process %s returned an error code (%u)"),Name,WEXITSTATUS(Status)); + + return _error->Error(_("Sub-process %s exited unexpectedly"),Name); + } + + return true; +} + /*}}}*/ +// StartsWithGPGClearTextSignature - Check if a file is Pgp/GPG clearsigned /*{{{*/ +bool StartsWithGPGClearTextSignature(string const &FileName) +{ + FILE* gpg = fopen(FileName.c_str(), "r"); + if (gpg == nullptr) + return false; + + char * lineptr = nullptr; + size_t n = 0; + errno = 0; + ssize_t const result = getline(&lineptr, &n, gpg); + if (errno != 0) + { + _error->Errno("getline", "Could not read from %s", FileName.c_str()); + fclose(gpg); + free(lineptr); + return false; + } + fclose(gpg); + + _strrstrip(lineptr); + static const char* SIGMSG = "-----BEGIN PGP SIGNED MESSAGE-----"; + if (result == -1 || strcmp(lineptr, SIGMSG) != 0) + { + free(lineptr); + return false; + } + free(lineptr); + return true; +} + /*}}}*/ +// ChangeOwnerAndPermissionOfFile - set file attributes to requested values /*{{{*/ +bool ChangeOwnerAndPermissionOfFile(char const * const requester, char const * const file, char const * const user, char const * const group, mode_t const mode) +{ + if (strcmp(file, "/dev/null") == 0) + return true; + bool Res = true; + if (getuid() == 0 && strlen(user) != 0 && strlen(group) != 0) // if we aren't root, we can't chown, so don't try it + { + // ensure the file is owned by root and has good permissions + struct passwd const * const pw = getpwnam(user); + struct group const * const gr = getgrnam(group); + if (pw != NULL && gr != NULL && lchown(file, pw->pw_uid, gr->gr_gid) != 0) + Res &= _error->WarningE(requester, "chown to %s:%s of file %s failed", user, group, file); + } + struct stat Buf; + if (lstat(file, &Buf) != 0 || S_ISLNK(Buf.st_mode)) + return Res; + if (chmod(file, mode) != 0) + Res &= _error->WarningE(requester, "chmod 0%o of file %s failed", mode, file); + return Res; +} + /*}}}*/ + +struct APT_HIDDEN simple_buffer { /*{{{*/ + size_t buffersize_max = 0; + unsigned long long bufferstart = 0; + unsigned long long bufferend = 0; + char *buffer = nullptr; + + simple_buffer() { + reset(4096); + } + ~simple_buffer() { + delete[] buffer; + } + + const char *get() const { return buffer + bufferstart; } + char *get() { return buffer + bufferstart; } + const char *getend() const { return buffer + bufferend; } + char *getend() { return buffer + bufferend; } + bool empty() const { return bufferend <= bufferstart; } + bool full() const { return bufferend == buffersize_max; } + unsigned long long free() const { return buffersize_max - bufferend; } + unsigned long long size() const { return bufferend-bufferstart; } + void reset(size_t size) + { + if (size > buffersize_max) { + delete[] buffer; + buffersize_max = size; + buffer = new char[size]; + } + reset(); + } + void reset() { bufferend = bufferstart = 0; } + ssize_t read(void *to, unsigned long long requested_size) APT_MUSTCHECK + { + if (size() < requested_size) + requested_size = size(); + memcpy(to, buffer + bufferstart, requested_size); + bufferstart += requested_size; + if (bufferstart == bufferend) + bufferstart = bufferend = 0; + return requested_size; + } + ssize_t write(const void *from, unsigned long long requested_size) APT_MUSTCHECK + { + if (free() < requested_size) + requested_size = free(); + memcpy(getend(), from, requested_size); + bufferend += requested_size; + if (bufferstart == bufferend) + bufferstart = bufferend = 0; + return requested_size; + } +}; + /*}}}*/ + +class APT_HIDDEN FileFdPrivate { /*{{{*/ + friend class BufferedWriteFileFdPrivate; +protected: + FileFd * const filefd; + simple_buffer buffer; + int compressed_fd; + pid_t compressor_pid; + bool is_pipe; + APT::Configuration::Compressor compressor; + unsigned int openmode; + unsigned long long seekpos; +public: + + explicit FileFdPrivate(FileFd * const pfilefd) : filefd(pfilefd), + compressed_fd(-1), compressor_pid(-1), is_pipe(false), + openmode(0), seekpos(0) {}; + virtual APT::Configuration::Compressor get_compressor() const + { + return compressor; + } + virtual void set_compressor(APT::Configuration::Compressor const &compressor) + { + this->compressor = compressor; + } + virtual unsigned int get_openmode() const + { + return openmode; + } + virtual void set_openmode(unsigned int openmode) + { + this->openmode = openmode; + } + virtual bool get_is_pipe() const + { + return is_pipe; + } + virtual void set_is_pipe(bool is_pipe) + { + this->is_pipe = is_pipe; + } + virtual unsigned long long get_seekpos() const + { + return seekpos; + } + virtual void set_seekpos(unsigned long long seekpos) + { + this->seekpos = seekpos; + } + + virtual bool InternalOpen(int const iFd, unsigned int const Mode) = 0; + ssize_t InternalRead(void * To, unsigned long long Size) + { + // Drain the buffer if needed. + if (buffer.empty() == false) + { + return buffer.read(To, Size); + } + return InternalUnbufferedRead(To, Size); + } + virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) = 0; + virtual bool InternalReadError() { return filefd->FileFdErrno("read",_("Read error")); } + virtual char * InternalReadLine(char * To, unsigned long long Size) + { + if (unlikely(Size == 0)) + return nullptr; + // Read one byte less than buffer size to have space for trailing 0. + --Size; + + char * const InitialTo = To; + + while (Size > 0) { + if (buffer.empty() == true) + { + buffer.reset(); + unsigned long long actualread = 0; + if (filefd->Read(buffer.getend(), buffer.free(), &actualread) == false) + return nullptr; + buffer.bufferend = actualread; + if (buffer.size() == 0) + { + if (To == InitialTo) + return nullptr; + break; + } + filefd->Flags &= ~FileFd::HitEof; + } + + unsigned long long const OutputSize = std::min(Size, buffer.size()); + char const * const newline = static_cast<char const *>(memchr(buffer.get(), '\n', OutputSize)); + // Read until end of line or up to Size bytes from the buffer. + unsigned long long actualread = buffer.read(To, + (newline != nullptr) + ? (newline - buffer.get()) + 1 + : OutputSize); + To += actualread; + Size -= actualread; + if (newline != nullptr) + break; + } + *To = '\0'; + return InitialTo; + } + virtual bool InternalFlush() + { + return true; + } + virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) = 0; + virtual bool InternalWriteError() { return filefd->FileFdErrno("write",_("Write error")); } + virtual bool InternalSeek(unsigned long long const To) + { + // Our poor man seeking is costly, so try to avoid it + unsigned long long const iseekpos = filefd->Tell(); + if (iseekpos == To) + return true; + else if (iseekpos < To) + return filefd->Skip(To - iseekpos); + + if ((openmode & FileFd::ReadOnly) != FileFd::ReadOnly) + return filefd->FileFdError("Reopen is only implemented for read-only files!"); + InternalClose(filefd->FileName); + if (filefd->iFd != -1) + close(filefd->iFd); + filefd->iFd = -1; + if (filefd->TemporaryFileName.empty() == false) + filefd->iFd = open(filefd->TemporaryFileName.c_str(), O_RDONLY); + else if (filefd->FileName.empty() == false) + filefd->iFd = open(filefd->FileName.c_str(), O_RDONLY); + else + { + if (compressed_fd > 0) + if (lseek(compressed_fd, 0, SEEK_SET) != 0) + filefd->iFd = compressed_fd; + if (filefd->iFd < 0) + return filefd->FileFdError("Reopen is not implemented for pipes opened with FileFd::OpenDescriptor()!"); + } + + if (filefd->OpenInternDescriptor(openmode, compressor) == false) + return filefd->FileFdError("Seek on file %s because it couldn't be reopened", filefd->FileName.c_str()); + + buffer.reset(); + set_seekpos(0); + if (To != 0) + return filefd->Skip(To); + + seekpos = To; + return true; + } + virtual bool InternalSkip(unsigned long long Over) + { + unsigned long long constexpr buffersize = 1024; + char buffer[buffersize]; + while (Over != 0) + { + unsigned long long toread = std::min(buffersize, Over); + if (filefd->Read(buffer, toread) == false) + return filefd->FileFdError("Unable to seek ahead %llu",Over); + Over -= toread; + } + return true; + } + virtual bool InternalTruncate(unsigned long long const) + { + return filefd->FileFdError("Truncating compressed files is not implemented (%s)", filefd->FileName.c_str()); + } + virtual unsigned long long InternalTell() + { + // In theory, we could just return seekpos here always instead of + // seeking around, but not all users of FileFd use always Seek() and co + // so d->seekpos isn't always true and we can just use it as a hint if + // we have nothing else, but not always as an authority… + return seekpos - buffer.size(); + } + virtual unsigned long long InternalSize() + { + unsigned long long size = 0; + unsigned long long const oldSeek = filefd->Tell(); + unsigned long long constexpr ignoresize = 1024; + char ignore[ignoresize]; + unsigned long long read = 0; + do { + if (filefd->Read(ignore, ignoresize, &read) == false) + { + filefd->Seek(oldSeek); + return 0; + } + } while(read != 0); + size = filefd->Tell(); + filefd->Seek(oldSeek); + return size; + } + virtual bool InternalClose(std::string const &FileName) = 0; + virtual bool InternalStream() const { return false; } + virtual bool InternalAlwaysAutoClose() const { return true; } + + virtual ~FileFdPrivate() {} +}; + /*}}}*/ +class APT_HIDDEN BufferedWriteFileFdPrivate : public FileFdPrivate { /*{{{*/ +protected: + FileFdPrivate *wrapped; + simple_buffer writebuffer; + +public: + + explicit BufferedWriteFileFdPrivate(FileFdPrivate *Priv) : + FileFdPrivate(Priv->filefd), wrapped(Priv) {}; + + virtual APT::Configuration::Compressor get_compressor() const APT_OVERRIDE + { + return wrapped->get_compressor(); + } + virtual void set_compressor(APT::Configuration::Compressor const &compressor) APT_OVERRIDE + { + return wrapped->set_compressor(compressor); + } + virtual unsigned int get_openmode() const APT_OVERRIDE + { + return wrapped->get_openmode(); + } + virtual void set_openmode(unsigned int openmode) APT_OVERRIDE + { + return wrapped->set_openmode(openmode); + } + virtual bool get_is_pipe() const APT_OVERRIDE + { + return wrapped->get_is_pipe(); + } + virtual void set_is_pipe(bool is_pipe) APT_OVERRIDE + { + FileFdPrivate::set_is_pipe(is_pipe); + wrapped->set_is_pipe(is_pipe); + } + virtual unsigned long long get_seekpos() const APT_OVERRIDE + { + return wrapped->get_seekpos(); + } + virtual void set_seekpos(unsigned long long seekpos) APT_OVERRIDE + { + return wrapped->set_seekpos(seekpos); + } + virtual bool InternalOpen(int const iFd, unsigned int const Mode) APT_OVERRIDE + { + if (InternalFlush() == false) + return false; + return wrapped->InternalOpen(iFd, Mode); + } + virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) APT_OVERRIDE + { + if (InternalFlush() == false) + return -1; + return wrapped->InternalUnbufferedRead(To, Size); + + } + virtual bool InternalReadError() APT_OVERRIDE + { + return wrapped->InternalReadError(); + } + virtual char * InternalReadLine(char * To, unsigned long long Size) APT_OVERRIDE + { + if (InternalFlush() == false) + return nullptr; + return wrapped->InternalReadLine(To, Size); + } + virtual bool InternalFlush() APT_OVERRIDE + { + while (writebuffer.empty() == false) { + auto written = wrapped->InternalWrite(writebuffer.get(), + writebuffer.size()); + // Ignore interrupted syscalls + if (written < 0 && errno == EINTR) + continue; + if (written < 0) + return wrapped->InternalWriteError(); + + writebuffer.bufferstart += written; + } + writebuffer.reset(); + return wrapped->InternalFlush(); + } + virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) APT_OVERRIDE + { + // Optimisation: If the buffer is empty and we have more to write than + // would fit in the buffer (or equal number of bytes), write directly. + if (writebuffer.empty() == true && Size >= writebuffer.free()) + return wrapped->InternalWrite(From, Size); + + // Write as much into the buffer as possible and then flush if needed + auto written = writebuffer.write(From, Size); + + if (writebuffer.full() && InternalFlush() == false) + return -1; + + return written; + } + virtual bool InternalWriteError() APT_OVERRIDE + { + return wrapped->InternalWriteError(); + } + virtual bool InternalSeek(unsigned long long const To) APT_OVERRIDE + { + if (InternalFlush() == false) + return false; + return wrapped->InternalSeek(To); + } + virtual bool InternalSkip(unsigned long long Over) APT_OVERRIDE + { + if (InternalFlush() == false) + return false; + return wrapped->InternalSkip(Over); + } + virtual bool InternalTruncate(unsigned long long const Size) APT_OVERRIDE + { + if (InternalFlush() == false) + return false; + return wrapped->InternalTruncate(Size); + } + virtual unsigned long long InternalTell() APT_OVERRIDE + { + if (InternalFlush() == false) + return -1; + return wrapped->InternalTell(); + } + virtual unsigned long long InternalSize() APT_OVERRIDE + { + if (InternalFlush() == false) + return -1; + return wrapped->InternalSize(); + } + virtual bool InternalClose(std::string const &FileName) APT_OVERRIDE + { + return wrapped->InternalClose(FileName); + } + virtual bool InternalAlwaysAutoClose() const APT_OVERRIDE + { + return wrapped->InternalAlwaysAutoClose(); + } + virtual ~BufferedWriteFileFdPrivate() + { + delete wrapped; + } +}; + /*}}}*/ +class APT_HIDDEN GzipFileFdPrivate: public FileFdPrivate { /*{{{*/ +#ifdef HAVE_ZLIB +public: + gzFile gz; + virtual bool InternalOpen(int const iFd, unsigned int const Mode) APT_OVERRIDE + { + if ((Mode & FileFd::ReadWrite) == FileFd::ReadWrite) + gz = gzdopen(iFd, "r+"); + else if ((Mode & FileFd::WriteOnly) == FileFd::WriteOnly) + gz = gzdopen(iFd, "w"); + else + gz = gzdopen(iFd, "r"); + filefd->Flags |= FileFd::Compressed; + return gz != nullptr; + } + virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) APT_OVERRIDE + { + return gzread(gz, To, Size); + } + virtual bool InternalReadError() APT_OVERRIDE + { + int err; + char const * const errmsg = gzerror(gz, &err); + if (err != Z_ERRNO) + return filefd->FileFdError("gzread: %s (%d: %s)", _("Read error"), err, errmsg); + return FileFdPrivate::InternalReadError(); + } + virtual char * InternalReadLine(char * To, unsigned long long Size) APT_OVERRIDE + { + return gzgets(gz, To, Size); + } + virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) APT_OVERRIDE + { + return gzwrite(gz,From,Size); + } + virtual bool InternalWriteError() APT_OVERRIDE + { + int err; + char const * const errmsg = gzerror(gz, &err); + if (err != Z_ERRNO) + return filefd->FileFdError("gzwrite: %s (%d: %s)", _("Write error"), err, errmsg); + return FileFdPrivate::InternalWriteError(); + } + virtual bool InternalSeek(unsigned long long const To) APT_OVERRIDE + { + off_t const res = gzseek(gz, To, SEEK_SET); + if (res != (off_t)To) + return filefd->FileFdError("Unable to seek to %llu", To); + seekpos = To; + buffer.reset(); + return true; + } + virtual bool InternalSkip(unsigned long long Over) APT_OVERRIDE + { + if (Over >= buffer.size()) + { + Over -= buffer.size(); + buffer.reset(); + } + else + { + buffer.bufferstart += Over; + return true; + } + if (Over == 0) + return true; + off_t const res = gzseek(gz, Over, SEEK_CUR); + if (res < 0) + return filefd->FileFdError("Unable to seek ahead %llu",Over); + seekpos = res; + return true; + } + virtual unsigned long long InternalTell() APT_OVERRIDE + { + return gztell(gz) - buffer.size(); + } + virtual unsigned long long InternalSize() APT_OVERRIDE + { + unsigned long long filesize = FileFdPrivate::InternalSize(); + // only check gzsize if we are actually a gzip file, just checking for + // "gz" is not sufficient as uncompressed files could be opened with + // gzopen in "direct" mode as well + if (filesize == 0 || gzdirect(gz)) + return filesize; + + off_t const oldPos = lseek(filefd->iFd, 0, SEEK_CUR); + /* unfortunately zlib.h doesn't provide a gzsize(), so we have to do + * this ourselves; the original (uncompressed) file size is the last 32 + * bits of the file */ + // FIXME: Size for gz-files is limited by 32bit… no largefile support + if (lseek(filefd->iFd, -4, SEEK_END) < 0) + { + filefd->FileFdErrno("lseek","Unable to seek to end of gzipped file"); + return 0; + } + uint32_t size = 0; + if (read(filefd->iFd, &size, 4) != 4) + { + filefd->FileFdErrno("read","Unable to read original size of gzipped file"); + return 0; + } + size = le32toh(size); + + if (lseek(filefd->iFd, oldPos, SEEK_SET) < 0) + { + filefd->FileFdErrno("lseek","Unable to seek in gzipped file"); + return 0; + } + return size; + } + virtual bool InternalClose(std::string const &FileName) APT_OVERRIDE + { + if (gz == nullptr) + return true; + int const e = gzclose(gz); + gz = nullptr; + // gzdclose() on empty files always fails with "buffer error" here, ignore that + if (e != 0 && e != Z_BUF_ERROR) + return _error->Errno("close",_("Problem closing the gzip file %s"), FileName.c_str()); + return true; + } + + explicit GzipFileFdPrivate(FileFd * const filefd) : FileFdPrivate(filefd), gz(nullptr) {} + virtual ~GzipFileFdPrivate() { InternalClose(""); } +#endif +}; + /*}}}*/ +class APT_HIDDEN Bz2FileFdPrivate: public FileFdPrivate { /*{{{*/ +#ifdef HAVE_BZ2 + BZFILE* bz2; +public: + virtual bool InternalOpen(int const iFd, unsigned int const Mode) APT_OVERRIDE + { + if ((Mode & FileFd::ReadWrite) == FileFd::ReadWrite) + bz2 = BZ2_bzdopen(iFd, "r+"); + else if ((Mode & FileFd::WriteOnly) == FileFd::WriteOnly) + bz2 = BZ2_bzdopen(iFd, "w"); + else + bz2 = BZ2_bzdopen(iFd, "r"); + filefd->Flags |= FileFd::Compressed; + return bz2 != nullptr; + } + virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) APT_OVERRIDE + { + return BZ2_bzread(bz2, To, Size); + } + virtual bool InternalReadError() APT_OVERRIDE + { + int err; + char const * const errmsg = BZ2_bzerror(bz2, &err); + if (err != BZ_IO_ERROR) + return filefd->FileFdError("BZ2_bzread: %s %s (%d: %s)", filefd->FileName.c_str(), _("Read error"), err, errmsg); + return FileFdPrivate::InternalReadError(); + } + virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) APT_OVERRIDE + { + return BZ2_bzwrite(bz2, (void*)From, Size); + } + virtual bool InternalWriteError() APT_OVERRIDE + { + int err; + char const * const errmsg = BZ2_bzerror(bz2, &err); + if (err != BZ_IO_ERROR) + return filefd->FileFdError("BZ2_bzwrite: %s %s (%d: %s)", filefd->FileName.c_str(), _("Write error"), err, errmsg); + return FileFdPrivate::InternalWriteError(); + } + virtual bool InternalStream() const APT_OVERRIDE { return true; } + virtual bool InternalClose(std::string const &) APT_OVERRIDE + { + if (bz2 == nullptr) + return true; + BZ2_bzclose(bz2); + bz2 = nullptr; + return true; + } + + explicit Bz2FileFdPrivate(FileFd * const filefd) : FileFdPrivate(filefd), bz2(nullptr) {} + virtual ~Bz2FileFdPrivate() { InternalClose(""); } +#endif +}; + /*}}}*/ +class APT_HIDDEN Lz4FileFdPrivate: public FileFdPrivate { /*{{{*/ + static constexpr unsigned long long LZ4_HEADER_SIZE = 19; + static constexpr unsigned long long LZ4_FOOTER_SIZE = 4; +#ifdef HAVE_LZ4 + LZ4F_decompressionContext_t dctx; + LZ4F_compressionContext_t cctx; + LZ4F_errorCode_t res; + FileFd backend; + simple_buffer lz4_buffer; + // Count of bytes that the decompressor expects to read next, or buffer size. + size_t next_to_load = APT_BUFFER_SIZE; +public: + virtual bool InternalOpen(int const iFd, unsigned int const Mode) APT_OVERRIDE + { + if ((Mode & FileFd::ReadWrite) == FileFd::ReadWrite) + return _error->Error("lz4 only supports write or read mode"); + + if ((Mode & FileFd::WriteOnly) == FileFd::WriteOnly) { + res = LZ4F_createCompressionContext(&cctx, LZ4F_VERSION); + lz4_buffer.reset(LZ4F_compressBound(APT_BUFFER_SIZE, nullptr) + + LZ4_HEADER_SIZE + LZ4_FOOTER_SIZE); + } else { + res = LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION); + lz4_buffer.reset(APT_BUFFER_SIZE); + } + + filefd->Flags |= FileFd::Compressed; + + if (LZ4F_isError(res)) + return false; + + unsigned int flags = (Mode & (FileFd::WriteOnly|FileFd::ReadOnly)); + if (backend.OpenDescriptor(iFd, flags, FileFd::None, true) == false) + return false; + + // Write the file header + if ((Mode & FileFd::WriteOnly) == FileFd::WriteOnly) + { + res = LZ4F_compressBegin(cctx, lz4_buffer.buffer, lz4_buffer.buffersize_max, nullptr); + if (LZ4F_isError(res) || backend.Write(lz4_buffer.buffer, res) == false) + return false; + } + + return true; + } + virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) APT_OVERRIDE + { + /* Keep reading as long as the compressor still wants to read */ + while (next_to_load) { + // Fill compressed buffer; + if (lz4_buffer.empty()) { + unsigned long long read; + /* Reset - if LZ4 decompressor wants to read more, allocate more */ + lz4_buffer.reset(next_to_load); + if (backend.Read(lz4_buffer.getend(), lz4_buffer.free(), &read) == false) + return -1; + lz4_buffer.bufferend += read; + + /* Expected EOF */ + if (read == 0) { + res = -1; + return filefd->FileFdError("LZ4F: %s %s", + filefd->FileName.c_str(), + _("Unexpected end of file")), -1; + } + } + // Drain compressed buffer as far as possible. + size_t in = lz4_buffer.size(); + size_t out = Size; + + res = LZ4F_decompress(dctx, To, &out, lz4_buffer.get(), &in, nullptr); + if (LZ4F_isError(res)) + return -1; + + next_to_load = res; + lz4_buffer.bufferstart += in; + + if (out != 0) + return out; + } + + return 0; + } + virtual bool InternalReadError() APT_OVERRIDE + { + char const * const errmsg = LZ4F_getErrorName(res); + + return filefd->FileFdError("LZ4F: %s %s (%zu: %s)", filefd->FileName.c_str(), _("Read error"), res, errmsg); + } + virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) APT_OVERRIDE + { + unsigned long long const towrite = std::min(APT_BUFFER_SIZE, Size); + + res = LZ4F_compressUpdate(cctx, + lz4_buffer.buffer, lz4_buffer.buffersize_max, + From, towrite, nullptr); + + if (LZ4F_isError(res) || backend.Write(lz4_buffer.buffer, res) == false) + return -1; + + return towrite; + } + virtual bool InternalWriteError() APT_OVERRIDE + { + char const * const errmsg = LZ4F_getErrorName(res); + + return filefd->FileFdError("LZ4F: %s %s (%zu: %s)", filefd->FileName.c_str(), _("Write error"), res, errmsg); + } + virtual bool InternalStream() const APT_OVERRIDE { return true; } + + virtual bool InternalFlush() APT_OVERRIDE + { + return backend.Flush(); + } + + virtual bool InternalClose(std::string const &) APT_OVERRIDE + { + /* Reset variables */ + res = 0; + next_to_load = APT_BUFFER_SIZE; + + if (cctx != nullptr) + { + if (filefd->Failed() == false) + { + res = LZ4F_compressEnd(cctx, lz4_buffer.buffer, lz4_buffer.buffersize_max, nullptr); + if (LZ4F_isError(res) || backend.Write(lz4_buffer.buffer, res) == false) + return false; + if (!backend.Flush()) + return false; + } + if (!backend.Close()) + return false; + + res = LZ4F_freeCompressionContext(cctx); + cctx = nullptr; + } + + if (dctx != nullptr) + { + res = LZ4F_freeDecompressionContext(dctx); + dctx = nullptr; + } + if (backend.IsOpen()) + { + backend.Close(); + filefd->iFd = -1; + } + + return LZ4F_isError(res) == false; + } + + explicit Lz4FileFdPrivate(FileFd * const filefd) : FileFdPrivate(filefd), dctx(nullptr), cctx(nullptr) {} + virtual ~Lz4FileFdPrivate() { + InternalClose(""); + } +#endif +}; + /*}}}*/ + +class APT_HIDDEN ZstdFileFdPrivate : public FileFdPrivate +{ /*{{{*/ +#ifdef HAVE_ZSTD + ZSTD_DStream *dctx; + ZSTD_CStream *cctx; + size_t res; + FileFd backend; + simple_buffer zstd_buffer; + // Count of bytes that the decompressor expects to read next, or buffer size. + size_t next_to_load = APT_BUFFER_SIZE; + + public: + virtual bool InternalOpen(int const iFd, unsigned int const Mode) APT_OVERRIDE + { + if ((Mode & FileFd::ReadWrite) == FileFd::ReadWrite) + return _error->Error("zstd only supports write or read mode"); + + if ((Mode & FileFd::WriteOnly) == FileFd::WriteOnly) + { + cctx = ZSTD_createCStream(); + res = ZSTD_initCStream(cctx, findLevel(compressor.CompressArgs)); + zstd_buffer.reset(APT_BUFFER_SIZE); + } + else + { + dctx = ZSTD_createDStream(); + res = ZSTD_initDStream(dctx); + zstd_buffer.reset(APT_BUFFER_SIZE); + } + + filefd->Flags |= FileFd::Compressed; + + if (ZSTD_isError(res)) + return false; + + unsigned int flags = (Mode & (FileFd::WriteOnly | FileFd::ReadOnly)); + if (backend.OpenDescriptor(iFd, flags, FileFd::None, true) == false) + return false; + + return true; + } + virtual ssize_t InternalUnbufferedRead(void *const To, unsigned long long const Size) APT_OVERRIDE + { + /* Keep reading as long as the compressor still wants to read */ + while (true) + { + // Fill compressed buffer; + if (zstd_buffer.empty()) + { + unsigned long long read; + /* Reset - if LZ4 decompressor wants to read more, allocate more */ + zstd_buffer.reset(next_to_load); + if (backend.Read(zstd_buffer.getend(), zstd_buffer.free(), &read) == false) + return -1; + zstd_buffer.bufferend += read; + + if (read == 0) + { + /* Expected EOF */ + if (next_to_load == 0) + return 0; + + res = -1; + return filefd->FileFdError("ZSTD: %s %s", + filefd->FileName.c_str(), + _("Unexpected end of file")), + -1; + } + } + // Drain compressed buffer as far as possible. + ZSTD_inBuffer in = { + .src = zstd_buffer.get(), + .size = zstd_buffer.size(), + .pos = 0, + }; + ZSTD_outBuffer out = { + .dst = To, + .size = Size, + .pos = 0, + }; + + next_to_load = res = ZSTD_decompressStream(dctx, &out, &in); + + if (res == 0) + { + res = ZSTD_initDStream(dctx); + } + + if (ZSTD_isError(res)) + return -1; + + zstd_buffer.bufferstart += in.pos; + + if (out.pos != 0) + return out.pos; + } + + return 0; + } + virtual bool InternalReadError() APT_OVERRIDE + { + char const *const errmsg = ZSTD_getErrorName(res); + + return filefd->FileFdError("ZSTD: %s %s (%zu: %s)", filefd->FileName.c_str(), _("Read error"), res, errmsg); + } + virtual ssize_t InternalWrite(void const *const From, unsigned long long const Size) APT_OVERRIDE + { + // Drain compressed buffer as far as possible. + ZSTD_outBuffer out = { + .dst = zstd_buffer.buffer, + .size = zstd_buffer.buffersize_max, + .pos = 0, + }; + ZSTD_inBuffer in = { + .src = From, + .size = Size, + .pos = 0, + }; + + res = ZSTD_compressStream(cctx, &out, &in); + + if (ZSTD_isError(res) || backend.Write(zstd_buffer.buffer, out.pos) == false) + return -1; + + return in.pos; + } + + virtual bool InternalWriteError() APT_OVERRIDE + { + char const *const errmsg = ZSTD_getErrorName(res); + + return filefd->FileFdError("ZSTD: %s %s (%zu: %s)", filefd->FileName.c_str(), _("Write error"), res, errmsg); + } + virtual bool InternalStream() const APT_OVERRIDE { return true; } + + virtual bool InternalFlush() APT_OVERRIDE + { + return backend.Flush(); + } + + virtual bool InternalClose(std::string const &) APT_OVERRIDE + { + /* Reset variables */ + res = 0; + next_to_load = APT_BUFFER_SIZE; + + if (cctx != nullptr) + { + if (filefd->Failed() == false) + { + do + { + ZSTD_outBuffer out = { + .dst = zstd_buffer.buffer, + .size = zstd_buffer.buffersize_max, + .pos = 0, + }; + res = ZSTD_endStream(cctx, &out); + if (ZSTD_isError(res) || backend.Write(zstd_buffer.buffer, out.pos) == false) + return false; + } while (res > 0); + + if (!backend.Flush()) + return false; + } + if (!backend.Close()) + return false; + + res = ZSTD_freeCStream(cctx); + cctx = nullptr; + } + + if (dctx != nullptr) + { + res = ZSTD_freeDStream(dctx); + dctx = nullptr; + } + if (backend.IsOpen()) + { + backend.Close(); + filefd->iFd = -1; + } + + return ZSTD_isError(res) == false; + } + + static uint32_t findLevel(std::vector<std::string> const &Args) + { + for (auto a = Args.rbegin(); a != Args.rend(); ++a) + { + if (a->size() >= 2 && (*a)[0] == '-' && (*a)[1] != '-') + { + auto const level = a->substr(1); + auto const notANumber = level.find_first_not_of("0123456789"); + if (notANumber != std::string::npos) + continue; + + return (uint32_t)stoi(level); + } + } + return 19; + } + + explicit ZstdFileFdPrivate(FileFd *const filefd) : FileFdPrivate(filefd), dctx(nullptr), cctx(nullptr) {} + virtual ~ZstdFileFdPrivate() + { + InternalClose(""); + } +#endif +}; + /*}}}*/ +class APT_HIDDEN LzmaFileFdPrivate: public FileFdPrivate { /*{{{*/ +#ifdef HAVE_LZMA + struct LZMAFILE { + FILE* file; + FileFd * const filefd; + uint8_t buffer[4096]; + lzma_stream stream; + lzma_ret err; + bool eof; + bool compressing; + + LZMAFILE(FileFd * const fd) : file(nullptr), filefd(fd), eof(false), compressing(false) { buffer[0] = '\0'; } + ~LZMAFILE() + { + if (compressing == true && filefd->Failed() == false) + { + size_t constexpr buffersize = sizeof(buffer)/sizeof(buffer[0]); + while(true) + { + stream.avail_out = buffersize; + stream.next_out = buffer; + err = lzma_code(&stream, LZMA_FINISH); + if (err != LZMA_OK && err != LZMA_STREAM_END) + { + _error->Error("~LZMAFILE: Compress finalisation failed"); + break; + } + size_t const n = buffersize - stream.avail_out; + if (n && fwrite(buffer, 1, n, file) != n) + { + _error->Errno("~LZMAFILE",_("Write error")); + break; + } + if (err == LZMA_STREAM_END) + break; + } + } + lzma_end(&stream); + fclose(file); + } + }; + LZMAFILE* lzma; + static uint32_t findXZlevel(std::vector<std::string> const &Args) + { + for (auto a = Args.rbegin(); a != Args.rend(); ++a) + if (a->empty() == false && (*a)[0] == '-' && (*a)[1] != '-') + { + auto const number = a->find_last_of("0123456789"); + if (number == std::string::npos) + continue; + auto const extreme = a->find("e", number); + uint32_t level = (extreme != std::string::npos) ? LZMA_PRESET_EXTREME : 0; + switch ((*a)[number]) + { + case '0': return level | 0; + case '1': return level | 1; + case '2': return level | 2; + case '3': return level | 3; + case '4': return level | 4; + case '5': return level | 5; + case '6': return level | 6; + case '7': return level | 7; + case '8': return level | 8; + case '9': return level | 9; + } + } + return 6; + } +public: + virtual bool InternalOpen(int const iFd, unsigned int const Mode) APT_OVERRIDE + { + if ((Mode & FileFd::ReadWrite) == FileFd::ReadWrite) + return filefd->FileFdError("ReadWrite mode is not supported for lzma/xz files %s", filefd->FileName.c_str()); + + if (lzma == nullptr) + lzma = new LzmaFileFdPrivate::LZMAFILE(filefd); + if ((Mode & FileFd::WriteOnly) == FileFd::WriteOnly) + lzma->file = fdopen(iFd, "w"); + else + lzma->file = fdopen(iFd, "r"); + filefd->Flags |= FileFd::Compressed; + if (lzma->file == nullptr) + return false; + + lzma_stream tmp_stream = LZMA_STREAM_INIT; + lzma->stream = tmp_stream; + + if ((Mode & FileFd::WriteOnly) == FileFd::WriteOnly) + { + uint32_t const xzlevel = findXZlevel(compressor.CompressArgs); + if (compressor.Name == "xz") + { + if (lzma_easy_encoder(&lzma->stream, xzlevel, LZMA_CHECK_CRC64) != LZMA_OK) + return false; + } + else + { + lzma_options_lzma options; + lzma_lzma_preset(&options, xzlevel); + if (lzma_alone_encoder(&lzma->stream, &options) != LZMA_OK) + return false; + } + lzma->compressing = true; + } + else + { + uint64_t const memlimit = UINT64_MAX; + if (compressor.Name == "xz") + { + if (lzma_auto_decoder(&lzma->stream, memlimit, 0) != LZMA_OK) + return false; + } + else + { + if (lzma_alone_decoder(&lzma->stream, memlimit) != LZMA_OK) + return false; + } + lzma->compressing = false; + } + return true; + } + virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) APT_OVERRIDE + { + ssize_t Res; + if (lzma->eof == true) + return 0; + + lzma->stream.next_out = (uint8_t *) To; + lzma->stream.avail_out = Size; + if (lzma->stream.avail_in == 0) + { + lzma->stream.next_in = lzma->buffer; + lzma->stream.avail_in = fread(lzma->buffer, 1, sizeof(lzma->buffer)/sizeof(lzma->buffer[0]), lzma->file); + } + lzma->err = lzma_code(&lzma->stream, LZMA_RUN); + if (lzma->err == LZMA_STREAM_END) + { + lzma->eof = true; + Res = Size - lzma->stream.avail_out; + } + else if (lzma->err != LZMA_OK) + { + Res = -1; + errno = 0; + } + else + { + Res = Size - lzma->stream.avail_out; + if (Res == 0) + { + // lzma run was okay, but produced no output… + Res = -1; + errno = EINTR; + } + } + return Res; + } + virtual bool InternalReadError() APT_OVERRIDE + { + return filefd->FileFdError("lzma_read: %s (%d)", _("Read error"), lzma->err); + } + virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) APT_OVERRIDE + { + ssize_t Res; + lzma->stream.next_in = (uint8_t *)From; + lzma->stream.avail_in = Size; + lzma->stream.next_out = lzma->buffer; + lzma->stream.avail_out = sizeof(lzma->buffer)/sizeof(lzma->buffer[0]); + lzma->err = lzma_code(&lzma->stream, LZMA_RUN); + if (lzma->err != LZMA_OK) + return -1; + size_t const n = sizeof(lzma->buffer)/sizeof(lzma->buffer[0]) - lzma->stream.avail_out; + size_t const m = (n == 0) ? 0 : fwrite(lzma->buffer, 1, n, lzma->file); + if (m != n) + { + Res = -1; + errno = 0; + } + else + { + Res = Size - lzma->stream.avail_in; + if (Res == 0) + { + // lzma run was okay, but produced no output… + Res = -1; + errno = EINTR; + } + } + return Res; + } + virtual bool InternalWriteError() APT_OVERRIDE + { + return filefd->FileFdError("lzma_write: %s (%d)", _("Write error"), lzma->err); + } + virtual bool InternalStream() const APT_OVERRIDE { return true; } + virtual bool InternalClose(std::string const &) APT_OVERRIDE + { + delete lzma; + lzma = nullptr; + return true; + } + + explicit LzmaFileFdPrivate(FileFd * const filefd) : FileFdPrivate(filefd), lzma(nullptr) {} + virtual ~LzmaFileFdPrivate() { InternalClose(""); } +#endif +}; + /*}}}*/ +class APT_HIDDEN PipedFileFdPrivate: public FileFdPrivate /*{{{*/ +/* if we don't have a specific class dealing with library calls, we (un)compress + by executing a specified binary and pipe in/out what we need */ +{ +public: + virtual bool InternalOpen(int const, unsigned int const Mode) APT_OVERRIDE + { + // collect zombies here in case we reopen + if (compressor_pid > 0) + ExecWait(compressor_pid, "FileFdCompressor", true); + + if ((Mode & FileFd::ReadWrite) == FileFd::ReadWrite) + return filefd->FileFdError("ReadWrite mode is not supported for file %s", filefd->FileName.c_str()); + if (compressor.Binary == "false") + return filefd->FileFdError("libapt has inbuilt support for the %s compression," + " but was forced to ignore it in favor of an external binary – which isn't installed.", compressor.Name.c_str()); + + bool const Comp = (Mode & FileFd::WriteOnly) == FileFd::WriteOnly; + if (Comp == false && filefd->iFd != -1) + { + // Handle 'decompression' of empty files + struct stat Buf; + if (fstat(filefd->iFd, &Buf) != 0) + return filefd->FileFdErrno("fstat", "Could not stat fd %d for file %s", filefd->iFd, filefd->FileName.c_str()); + if (Buf.st_size == 0 && S_ISFIFO(Buf.st_mode) == false) + return true; + + // We don't need the file open - instead let the compressor open it + // as he properly knows better how to efficiently read from 'his' file + if (filefd->FileName.empty() == false) + { + close(filefd->iFd); + filefd->iFd = -1; + } + } + + // Create a data pipe + int Pipe[2] = {-1,-1}; + if (pipe(Pipe) != 0) + return filefd->FileFdErrno("pipe",_("Failed to create subprocess IPC")); + for (int J = 0; J != 2; J++) + SetCloseExec(Pipe[J],true); + + compressed_fd = filefd->iFd; + set_is_pipe(true); + + if (Comp == true) + filefd->iFd = Pipe[1]; + else + filefd->iFd = Pipe[0]; + + // The child.. + compressor_pid = ExecFork(); + if (compressor_pid == 0) + { + if (Comp == true) + { + dup2(compressed_fd,STDOUT_FILENO); + dup2(Pipe[0],STDIN_FILENO); + } + else + { + if (compressed_fd != -1) + dup2(compressed_fd,STDIN_FILENO); + dup2(Pipe[1],STDOUT_FILENO); + } + int const nullfd = open("/dev/null", O_WRONLY); + if (nullfd != -1) + { + dup2(nullfd,STDERR_FILENO); + close(nullfd); + } + + SetCloseExec(STDOUT_FILENO,false); + SetCloseExec(STDIN_FILENO,false); + + std::vector<char const*> Args; + Args.push_back(compressor.Binary.c_str()); + std::vector<std::string> const * const addArgs = + (Comp == true) ? &(compressor.CompressArgs) : &(compressor.UncompressArgs); + for (std::vector<std::string>::const_iterator a = addArgs->begin(); + a != addArgs->end(); ++a) + Args.push_back(a->c_str()); + if (Comp == false && filefd->FileName.empty() == false) + { + // commands not needing arguments, do not need to be told about using standard output + // in reality, only testcases with tools like cat, rev, rot13, … are able to trigger this + if (compressor.CompressArgs.empty() == false && compressor.UncompressArgs.empty() == false) + Args.push_back("--stdout"); + if (filefd->TemporaryFileName.empty() == false) + Args.push_back(filefd->TemporaryFileName.c_str()); + else + Args.push_back(filefd->FileName.c_str()); + } + Args.push_back(NULL); + + execvp(Args[0],(char **)&Args[0]); + cerr << _("Failed to exec compressor ") << Args[0] << endl; + _exit(100); + } + if (Comp == true) + close(Pipe[0]); + else + close(Pipe[1]); + + return true; + } + virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) APT_OVERRIDE + { + return read(filefd->iFd, To, Size); + } + virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) APT_OVERRIDE + { + return write(filefd->iFd, From, Size); + } + virtual bool InternalClose(std::string const &) APT_OVERRIDE + { + bool Ret = true; + if (filefd->iFd != -1) + { + close(filefd->iFd); + filefd->iFd = -1; + } + if (compressor_pid > 0) + Ret &= ExecWait(compressor_pid, "FileFdCompressor", true); + compressor_pid = -1; + return Ret; + } + explicit PipedFileFdPrivate(FileFd * const filefd) : FileFdPrivate(filefd) {} + virtual ~PipedFileFdPrivate() { InternalClose(""); } +}; + /*}}}*/ +class APT_HIDDEN DirectFileFdPrivate: public FileFdPrivate /*{{{*/ +{ +public: + virtual bool InternalOpen(int const, unsigned int const) APT_OVERRIDE { return true; } + virtual ssize_t InternalUnbufferedRead(void * const To, unsigned long long const Size) APT_OVERRIDE + { + return read(filefd->iFd, To, Size); + } + virtual ssize_t InternalWrite(void const * const From, unsigned long long const Size) APT_OVERRIDE + { + // files opened read+write are strange and only really "supported" for direct files + if (buffer.size() != 0) + { + lseek(filefd->iFd, -buffer.size(), SEEK_CUR); + buffer.reset(); + } + return write(filefd->iFd, From, Size); + } + virtual bool InternalSeek(unsigned long long const To) APT_OVERRIDE + { + off_t const res = lseek(filefd->iFd, To, SEEK_SET); + if (res != (off_t)To) + return filefd->FileFdError("Unable to seek to %llu", To); + seekpos = To; + buffer.reset(); + return true; + } + virtual bool InternalSkip(unsigned long long Over) APT_OVERRIDE + { + if (Over >= buffer.size()) + { + Over -= buffer.size(); + buffer.reset(); + } + else + { + buffer.bufferstart += Over; + return true; + } + if (Over == 0) + return true; + off_t const res = lseek(filefd->iFd, Over, SEEK_CUR); + if (res < 0) + return filefd->FileFdError("Unable to seek ahead %llu",Over); + seekpos = res; + return true; + } + virtual bool InternalTruncate(unsigned long long const To) APT_OVERRIDE + { + if (buffer.size() != 0) + { + unsigned long long const seekpos = lseek(filefd->iFd, 0, SEEK_CUR); + if ((seekpos - buffer.size()) >= To) + buffer.reset(); + else if (seekpos >= To) + buffer.bufferend = (To - seekpos) + buffer.bufferstart; + else + buffer.reset(); + } + if (ftruncate(filefd->iFd, To) != 0) + return filefd->FileFdError("Unable to truncate to %llu",To); + return true; + } + virtual unsigned long long InternalTell() APT_OVERRIDE + { + return lseek(filefd->iFd,0,SEEK_CUR) - buffer.size(); + } + virtual unsigned long long InternalSize() APT_OVERRIDE + { + return filefd->FileSize(); + } + virtual bool InternalClose(std::string const &) APT_OVERRIDE { return true; } + virtual bool InternalAlwaysAutoClose() const APT_OVERRIDE { return false; } + + explicit DirectFileFdPrivate(FileFd * const filefd) : FileFdPrivate(filefd) {} + virtual ~DirectFileFdPrivate() { InternalClose(""); } +}; + /*}}}*/ +// FileFd Constructors /*{{{*/ +FileFd::FileFd(std::string FileName,unsigned int const Mode,unsigned long AccessMode) : iFd(-1), Flags(0), d(NULL) +{ + Open(FileName,Mode, None, AccessMode); +} +FileFd::FileFd(std::string FileName,unsigned int const Mode, CompressMode Compress, unsigned long AccessMode) : iFd(-1), Flags(0), d(NULL) +{ + Open(FileName,Mode, Compress, AccessMode); +} +FileFd::FileFd() : iFd(-1), Flags(AutoClose), d(NULL) {} +FileFd::FileFd(int const Fd, unsigned int const Mode, CompressMode Compress) : iFd(-1), Flags(0), d(NULL) +{ + OpenDescriptor(Fd, Mode, Compress); +} +FileFd::FileFd(int const Fd, bool const AutoClose) : iFd(-1), Flags(0), d(NULL) +{ + OpenDescriptor(Fd, ReadWrite, None, AutoClose); +} + /*}}}*/ +// FileFd::Open - Open a file /*{{{*/ +// --------------------------------------------------------------------- +/* The most commonly used open mode combinations are given with Mode */ +bool FileFd::Open(string FileName,unsigned int const Mode,CompressMode Compress, unsigned long const AccessMode) +{ + if (Mode == ReadOnlyGzip) + return Open(FileName, ReadOnly, Gzip, AccessMode); + + if (Compress == Auto && (Mode & WriteOnly) == WriteOnly) + return FileFdError("Autodetection on %s only works in ReadOnly openmode!", FileName.c_str()); + + std::vector<APT::Configuration::Compressor> const compressors = APT::Configuration::getCompressors(); + std::vector<APT::Configuration::Compressor>::const_iterator compressor = compressors.begin(); + if (Compress == Auto) + { + for (; compressor != compressors.end(); ++compressor) + { + std::string file = FileName + compressor->Extension; + if (FileExists(file) == false) + continue; + FileName = file; + break; + } + } + else if (Compress == Extension) + { + std::string::size_type const found = FileName.find_last_of('.'); + std::string ext; + if (found != std::string::npos) + { + ext = FileName.substr(found); + if (ext == ".new" || ext == ".bak") + { + std::string::size_type const found2 = FileName.find_last_of('.', found - 1); + if (found2 != std::string::npos) + ext = FileName.substr(found2, found - found2); + else + ext.clear(); + } + } + for (; compressor != compressors.end(); ++compressor) + if (ext == compressor->Extension) + break; + // no matching extension - assume uncompressed (imagine files like 'example.org_Packages') + if (compressor == compressors.end()) + for (compressor = compressors.begin(); compressor != compressors.end(); ++compressor) + if (compressor->Name == ".") + break; + } + else + { + std::string name; + switch (Compress) + { + case None: name = "."; break; + case Gzip: name = "gzip"; break; + case Bzip2: name = "bzip2"; break; + case Lzma: name = "lzma"; break; + case Xz: name = "xz"; break; + case Lz4: name = "lz4"; break; + case Zstd: name = "zstd"; break; + case Auto: + case Extension: + // Unreachable + return FileFdError("Opening File %s in None, Auto or Extension should be already handled?!?", FileName.c_str()); + } + for (; compressor != compressors.end(); ++compressor) + if (compressor->Name == name) + break; + if (compressor == compressors.end()) + return FileFdError("Can't find a configured compressor %s for file %s", name.c_str(), FileName.c_str()); + } + + if (compressor == compressors.end()) + return FileFdError("Can't find a match for specified compressor mode for file %s", FileName.c_str()); + return Open(FileName, Mode, *compressor, AccessMode); +} +bool FileFd::Open(string FileName,unsigned int const Mode,APT::Configuration::Compressor const &compressor, unsigned long const AccessMode) +{ + Close(); + Flags = AutoClose; + + if ((Mode & WriteOnly) != WriteOnly && (Mode & (Atomic | Create | Empty | Exclusive)) != 0) + return FileFdError("ReadOnly mode for %s doesn't accept additional flags!", FileName.c_str()); + if ((Mode & ReadWrite) == 0) + return FileFdError("No openmode provided in FileFd::Open for %s", FileName.c_str()); + + unsigned int OpenMode = Mode; + if (FileName == "/dev/null") + OpenMode = OpenMode & ~(Atomic | Exclusive | Create | Empty); + + if ((OpenMode & Atomic) == Atomic) + { + Flags |= Replace; + } + else if ((OpenMode & (Exclusive | Create)) == (Exclusive | Create)) + { + // for atomic, this will be done by rename in Close() + RemoveFile("FileFd::Open", FileName); + } + if ((OpenMode & Empty) == Empty) + { + struct stat Buf; + if (lstat(FileName.c_str(),&Buf) == 0 && S_ISLNK(Buf.st_mode)) + RemoveFile("FileFd::Open", FileName); + } + + int fileflags = 0; + #define if_FLAGGED_SET(FLAG, MODE) if ((OpenMode & FLAG) == FLAG) fileflags |= MODE + if_FLAGGED_SET(ReadWrite, O_RDWR); + else if_FLAGGED_SET(ReadOnly, O_RDONLY); + else if_FLAGGED_SET(WriteOnly, O_WRONLY); + + if_FLAGGED_SET(Create, O_CREAT); + if_FLAGGED_SET(Empty, O_TRUNC); + if_FLAGGED_SET(Exclusive, O_EXCL); + #undef if_FLAGGED_SET + + if ((OpenMode & Atomic) == Atomic) + { + char *name = strdup((FileName + ".XXXXXX").c_str()); + + if((iFd = mkstemp(name)) == -1) + { + free(name); + return FileFdErrno("mkstemp", "Could not create temporary file for %s", FileName.c_str()); + } + + TemporaryFileName = string(name); + free(name); + + // umask() will always set the umask and return the previous value, so + // we first set the umask and then reset it to the old value + mode_t const CurrentUmask = umask(0); + umask(CurrentUmask); + // calculate the actual file permissions (just like open/creat) + mode_t const FilePermissions = (AccessMode & ~CurrentUmask); + + if(fchmod(iFd, FilePermissions) == -1) + return FileFdErrno("fchmod", "Could not change permissions for temporary file %s", TemporaryFileName.c_str()); + } + else + iFd = open(FileName.c_str(), fileflags, AccessMode); + + this->FileName = FileName; + if (iFd == -1 || OpenInternDescriptor(OpenMode, compressor) == false) + { + if (iFd != -1) + { + close (iFd); + iFd = -1; + } + return FileFdErrno("open",_("Could not open file %s"), FileName.c_str()); + } + + SetCloseExec(iFd,true); + return true; +} + /*}}}*/ +// FileFd::OpenDescriptor - Open a filedescriptor /*{{{*/ +bool FileFd::OpenDescriptor(int Fd, unsigned int const Mode, CompressMode Compress, bool AutoClose) +{ + std::vector<APT::Configuration::Compressor> const compressors = APT::Configuration::getCompressors(); + std::vector<APT::Configuration::Compressor>::const_iterator compressor = compressors.begin(); + std::string name; + + // compat with the old API + if (Mode == ReadOnlyGzip && Compress == None) + Compress = Gzip; + + switch (Compress) + { + case None: name = "."; break; + case Gzip: name = "gzip"; break; + case Bzip2: name = "bzip2"; break; + case Lzma: name = "lzma"; break; + case Xz: name = "xz"; break; + case Lz4: name = "lz4"; break; + case Zstd: name = "zstd"; break; + case Auto: + case Extension: + if (AutoClose == true && Fd != -1) + close(Fd); + return FileFdError("Opening Fd %d in Auto or Extension compression mode is not supported", Fd); + } + for (; compressor != compressors.end(); ++compressor) + if (compressor->Name == name) + break; + if (compressor == compressors.end()) + { + if (AutoClose == true && Fd != -1) + close(Fd); + return FileFdError("Can't find a configured compressor %s for file %s", name.c_str(), FileName.c_str()); + } + return OpenDescriptor(Fd, Mode, *compressor, AutoClose); +} +bool FileFd::OpenDescriptor(int Fd, unsigned int const Mode, APT::Configuration::Compressor const &compressor, bool AutoClose) +{ + Close(); + Flags = (AutoClose) ? FileFd::AutoClose : 0; + iFd = Fd; + this->FileName = ""; + if (OpenInternDescriptor(Mode, compressor) == false) + { + if (iFd != -1 && ( + (Flags & Compressed) == Compressed || + AutoClose == true)) + { + close (iFd); + iFd = -1; + } + return FileFdError(_("Could not open file descriptor %d"), Fd); + } + return true; +} +bool FileFd::OpenInternDescriptor(unsigned int const Mode, APT::Configuration::Compressor const &compressor) +{ + if (iFd == -1) + return false; + + if (d != nullptr) + d->InternalClose(FileName); + + if (d == nullptr) + { + if (false) + /* dummy so that the rest can be 'else if's */; +#define APT_COMPRESS_INIT(NAME, CONSTRUCTOR) \ + else if (compressor.Name == NAME) \ + d = new CONSTRUCTOR(this) +#ifdef HAVE_ZLIB + APT_COMPRESS_INIT("gzip", GzipFileFdPrivate); +#endif +#ifdef HAVE_BZ2 + APT_COMPRESS_INIT("bzip2", Bz2FileFdPrivate); +#endif +#ifdef HAVE_LZMA + APT_COMPRESS_INIT("xz", LzmaFileFdPrivate); + APT_COMPRESS_INIT("lzma", LzmaFileFdPrivate); +#endif +#ifdef HAVE_LZ4 + APT_COMPRESS_INIT("lz4", Lz4FileFdPrivate); +#endif +#ifdef HAVE_ZSTD + APT_COMPRESS_INIT("zstd", ZstdFileFdPrivate); +#endif +#undef APT_COMPRESS_INIT + else if (compressor.Name == "." || compressor.Binary.empty() == true) + d = new DirectFileFdPrivate(this); + else + d = new PipedFileFdPrivate(this); + + if (Mode & BufferedWrite) + d = new BufferedWriteFileFdPrivate(d); + + d->set_openmode(Mode); + d->set_compressor(compressor); + if ((Flags & AutoClose) != AutoClose && d->InternalAlwaysAutoClose()) + { + // Need to duplicate fd here or gz/bz2 close for cleanup will close the fd as well + int const internFd = dup(iFd); + if (internFd == -1) + return FileFdErrno("OpenInternDescriptor", _("Could not open file descriptor %d"), iFd); + iFd = internFd; + } + } + return d->InternalOpen(iFd, Mode); +} + /*}}}*/ +// FileFd::~File - Closes the file /*{{{*/ +// --------------------------------------------------------------------- +/* If the proper modes are selected then we close the Fd and possibly + unlink the file on error. */ +FileFd::~FileFd() +{ + Close(); + if (d != NULL) + d->InternalClose(FileName); + delete d; + d = NULL; +} + /*}}}*/ +// FileFd::Read - Read a bit of the file /*{{{*/ +// --------------------------------------------------------------------- +/* We are careful to handle interruption by a signal while reading + gracefully. */ +bool FileFd::Read(void *To,unsigned long long Size,unsigned long long *Actual) +{ + if (d == nullptr || Failed()) + return false; + ssize_t Res = 1; + errno = 0; + if (Actual != 0) + *Actual = 0; + *((char *)To) = '\0'; + while (Res > 0 && Size > 0) + { + Res = d->InternalRead(To, Size); + + if (Res < 0) + { + if (errno == EINTR) + { + // trick the while-loop into running again + Res = 1; + errno = 0; + continue; + } + return d->InternalReadError(); + } + + To = (char *)To + Res; + Size -= Res; + if (d != NULL) + d->set_seekpos(d->get_seekpos() + Res); + if (Actual != 0) + *Actual += Res; + } + + if (Size == 0) + return true; + + // Eof handling + if (Actual != 0) + { + Flags |= HitEof; + return true; + } + + return FileFdError(_("read, still have %llu to read but none left"), Size); +} +bool FileFd::Read(int const Fd, void *To, unsigned long long Size, unsigned long long * const Actual) +{ + ssize_t Res = 1; + errno = 0; + if (Actual != nullptr) + *Actual = 0; + *static_cast<char *>(To) = '\0'; + while (Res > 0 && Size > 0) + { + Res = read(Fd, To, Size); + if (Res < 0) + { + if (errno == EINTR) + { + Res = 1; + errno = 0; + continue; + } + return _error->Errno("read", _("Read error")); + } + To = static_cast<char *>(To) + Res; + Size -= Res; + if (Actual != 0) + *Actual += Res; + } + if (Size == 0) + return true; + if (Actual != nullptr) + return true; + return _error->Error(_("read, still have %llu to read but none left"), Size); +} + /*}}}*/ +// FileFd::ReadLine - Read a complete line from the file /*{{{*/ +char* FileFd::ReadLine(char *To, unsigned long long const Size) +{ + *To = '\0'; + if (d == nullptr || Failed()) + return nullptr; + return d->InternalReadLine(To, Size); +} +bool FileFd::ReadLine(std::string &To) +{ + To.clear(); + if (d == nullptr || Failed()) + return false; + constexpr size_t buflen = 4096; + char buffer[buflen]; + size_t len; + do + { + if (d->InternalReadLine(buffer, buflen) == nullptr) + return false; + len = strlen(buffer); + To.append(buffer, len); + } while (len == buflen - 1 && buffer[len - 2] != '\n'); + // remove the newline at the end + auto const i = To.find_last_not_of("\r\n"); + if (i == std::string::npos) + To.clear(); + else + To.erase(i + 1); + return true; +} + /*}}}*/ +// FileFd::Flush - Flush the file /*{{{*/ +bool FileFd::Flush() +{ + if (Failed()) + return false; + if (d == nullptr) + return true; + + return d->InternalFlush(); +} + /*}}}*/ +// FileFd::Write - Write to the file /*{{{*/ +bool FileFd::Write(const void *From,unsigned long long Size) +{ + if (d == nullptr || Failed()) + return false; + ssize_t Res = 1; + errno = 0; + while (Res > 0 && Size > 0) + { + Res = d->InternalWrite(From, Size); + + if (Res < 0) + { + if (errno == EINTR) + { + // trick the while-loop into running again + Res = 1; + errno = 0; + continue; + } + return d->InternalWriteError(); + } + + From = (char const *)From + Res; + Size -= Res; + if (d != NULL) + d->set_seekpos(d->get_seekpos() + Res); + } + + if (Size == 0) + return true; + + return FileFdError(_("write, still have %llu to write but couldn't"), Size); +} +bool FileFd::Write(int Fd, const void *From, unsigned long long Size) +{ + ssize_t Res = 1; + errno = 0; + while (Res > 0 && Size > 0) + { + Res = write(Fd,From,Size); + if (Res < 0 && errno == EINTR) + continue; + if (Res < 0) + return _error->Errno("write",_("Write error")); + + From = (char const *)From + Res; + Size -= Res; + } + + if (Size == 0) + return true; + + return _error->Error(_("write, still have %llu to write but couldn't"), Size); +} + /*}}}*/ +// FileFd::Seek - Seek in the file /*{{{*/ +bool FileFd::Seek(unsigned long long To) +{ + if (d == nullptr || Failed()) + return false; + Flags &= ~HitEof; + return d->InternalSeek(To); +} + /*}}}*/ +// FileFd::Skip - Skip over data in the file /*{{{*/ +bool FileFd::Skip(unsigned long long Over) +{ + if (d == nullptr || Failed()) + return false; + return d->InternalSkip(Over); +} + /*}}}*/ +// FileFd::Truncate - Truncate the file /*{{{*/ +bool FileFd::Truncate(unsigned long long To) +{ + if (d == nullptr || Failed()) + return false; + // truncating /dev/null is always successful - as we get an error otherwise + if (To == 0 && FileName == "/dev/null") + return true; + return d->InternalTruncate(To); +} + /*}}}*/ +// FileFd::Tell - Current seek position /*{{{*/ +// --------------------------------------------------------------------- +/* */ +unsigned long long FileFd::Tell() +{ + if (d == nullptr || Failed()) + return false; + off_t const Res = d->InternalTell(); + if (Res == (off_t)-1) + FileFdErrno("lseek","Failed to determine the current file position"); + d->set_seekpos(Res); + return Res; +} + /*}}}*/ +static bool StatFileFd(char const * const msg, int const iFd, std::string const &FileName, struct stat &Buf, FileFdPrivate * const d) /*{{{*/ +{ + bool ispipe = (d != NULL && d->get_is_pipe() == true); + if (ispipe == false) + { + if (fstat(iFd,&Buf) != 0) + // higher-level code will generate more meaningful messages, + // even translated this would be meaningless for users + return _error->Errno("fstat", "Unable to determine %s for fd %i", msg, iFd); + if (FileName.empty() == false) + ispipe = S_ISFIFO(Buf.st_mode); + } + + // for compressor pipes st_size is undefined and at 'best' zero + if (ispipe == true) + { + // we set it here, too, as we get the info here for free + // in theory the Open-methods should take care of it already + if (d != NULL) + d->set_is_pipe(true); + if (stat(FileName.c_str(), &Buf) != 0) + return _error->Errno("fstat", "Unable to determine %s for file %s", msg, FileName.c_str()); + } + return true; +} + /*}}}*/ +// FileFd::FileSize - Return the size of the file /*{{{*/ +unsigned long long FileFd::FileSize() +{ + struct stat Buf; + if (StatFileFd("file size", iFd, FileName, Buf, d) == false) + { + Flags |= Fail; + return 0; + } + return Buf.st_size; +} + /*}}}*/ +// FileFd::ModificationTime - Return the time of last touch /*{{{*/ +time_t FileFd::ModificationTime() +{ + struct stat Buf; + if (StatFileFd("modification time", iFd, FileName, Buf, d) == false) + { + Flags |= Fail; + return 0; + } + return Buf.st_mtime; +} + /*}}}*/ +// FileFd::Size - Return the size of the content in the file /*{{{*/ +unsigned long long FileFd::Size() +{ + if (d == nullptr) + return 0; + return d->InternalSize(); +} + /*}}}*/ +// FileFd::Close - Close the file if the close flag is set /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool FileFd::Close() +{ + if (Failed() == false && Flush() == false) + return false; + if (iFd == -1) + return true; + + bool Res = true; + if ((Flags & AutoClose) == AutoClose) + { + if ((Flags & Compressed) != Compressed && iFd > 0 && close(iFd) != 0) + Res &= _error->Errno("close",_("Problem closing the file %s"), FileName.c_str()); + } + + if (d != NULL) + { + Res &= d->InternalClose(FileName); + delete d; + d = NULL; + } + + if ((Flags & Replace) == Replace) { + if (Failed() == false && rename(TemporaryFileName.c_str(), FileName.c_str()) != 0) + Res &= _error->Errno("rename",_("Problem renaming the file %s to %s"), TemporaryFileName.c_str(), FileName.c_str()); + + FileName = TemporaryFileName; // for the unlink() below. + TemporaryFileName.clear(); + } + + iFd = -1; + + if ((Flags & Fail) == Fail && (Flags & DelOnFail) == DelOnFail && + FileName.empty() == false) + Res &= RemoveFile("FileFd::Close", FileName); + + if (Res == false) + Flags |= Fail; + return Res; +} + /*}}}*/ +// FileFd::Sync - Sync the file /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool FileFd::Sync() +{ + if (fsync(iFd) != 0) + return FileFdErrno("sync",_("Problem syncing the file")); + return true; +} + /*}}}*/ +// FileFd::FileFdErrno - set Fail and call _error->Errno *{{{*/ +bool FileFd::FileFdErrno(const char *Function, const char *Description,...) +{ + Flags |= Fail; + va_list args; + size_t msgSize = 400; + int const errsv = errno; + bool retry; + do { + va_start(args,Description); + retry = _error->InsertErrno(GlobalError::ERROR, Function, Description, args, errsv, msgSize); + va_end(args); + } while (retry); + return false; +} + /*}}}*/ +// FileFd::FileFdError - set Fail and call _error->Error *{{{*/ +bool FileFd::FileFdError(const char *Description,...) { + Flags |= Fail; + va_list args; + size_t msgSize = 400; + bool retry; + do { + va_start(args,Description); + retry = _error->Insert(GlobalError::ERROR, Description, args, msgSize); + va_end(args); + } while (retry); + return false; +} + /*}}}*/ +gzFile FileFd::gzFd() { /*{{{*/ +#ifdef HAVE_ZLIB + GzipFileFdPrivate * const gzipd = dynamic_cast<GzipFileFdPrivate*>(d); + if (gzipd == nullptr) + return nullptr; + else + return gzipd->gz; +#else + return nullptr; +#endif +} + /*}}}*/ + +// Glob - wrapper around "glob()" /*{{{*/ +std::vector<std::string> Glob(std::string const &pattern, int flags) +{ + std::vector<std::string> result; + glob_t globbuf; + int glob_res; + unsigned int i; + + glob_res = glob(pattern.c_str(), flags, NULL, &globbuf); + + if (glob_res != 0) + { + if(glob_res != GLOB_NOMATCH) { + _error->Errno("glob", "Problem with glob"); + return result; + } + } + + // append results + for(i=0;i<globbuf.gl_pathc;i++) + result.push_back(string(globbuf.gl_pathv[i])); + + globfree(&globbuf); + return result; +} + /*}}}*/ +static std::string APT_NONNULL(1) GetTempDirEnv(char const * const env) /*{{{*/ +{ + const char *tmpdir = getenv(env); + +#ifdef P_tmpdir + if (!tmpdir) + tmpdir = P_tmpdir; +#endif + + struct stat st; + if (!tmpdir || strlen(tmpdir) == 0 || // tmpdir is set + stat(tmpdir, &st) != 0 || (st.st_mode & S_IFDIR) == 0) // exists and is directory + tmpdir = "/tmp"; + else if (geteuid() != 0 && // root can do everything anyway + faccessat(AT_FDCWD, tmpdir, R_OK | W_OK | X_OK, AT_EACCESS) != 0) // current user has rwx access to directory + tmpdir = "/tmp"; + + return string(tmpdir); +} + /*}}}*/ +std::string GetTempDir() /*{{{*/ +{ + return GetTempDirEnv("TMPDIR"); +} +std::string GetTempDir(std::string const &User) +{ + // no need/possibility to drop privs + if(getuid() != 0 || User.empty() || User == "root") + return GetTempDir(); + + struct passwd const * const pw = getpwnam(User.c_str()); + if (pw == NULL) + return GetTempDir(); + + gid_t const old_euid = geteuid(); + gid_t const old_egid = getegid(); + if (setegid(pw->pw_gid) != 0) + _error->Errno("setegid", "setegid %u failed", pw->pw_gid); + if (seteuid(pw->pw_uid) != 0) + _error->Errno("seteuid", "seteuid %u failed", pw->pw_uid); + + std::string const tmp = GetTempDir(); + + if (seteuid(old_euid) != 0) + _error->Errno("seteuid", "seteuid %u failed", old_euid); + if (setegid(old_egid) != 0) + _error->Errno("setegid", "setegid %u failed", old_egid); + + return tmp; +} + /*}}}*/ +FileFd* GetTempFile(std::string const &Prefix, bool ImmediateUnlink, FileFd * const TmpFd) /*{{{*/ +{ + return GetTempFile(Prefix, ImmediateUnlink, TmpFd, false); +} +FileFd* GetTempFile(std::string const &Prefix, bool ImmediateUnlink, FileFd * const TmpFd, bool Buffered) +{ + char fn[512]; + FileFd * const Fd = TmpFd == nullptr ? new FileFd() : TmpFd; + + std::string const tempdir = GetTempDir(); + snprintf(fn, sizeof(fn), "%s/%s.XXXXXX", + tempdir.c_str(), Prefix.c_str()); + int const fd = mkstemp(fn); + if (ImmediateUnlink) + unlink(fn); + if (fd < 0) + { + _error->Errno("GetTempFile",_("Unable to mkstemp %s"), fn); + if (TmpFd == nullptr) + delete Fd; + return nullptr; + } + if (!Fd->OpenDescriptor(fd, FileFd::ReadWrite | (Buffered ? FileFd::BufferedWrite : 0), FileFd::None, true)) + { + _error->Errno("GetTempFile",_("Unable to write to %s"),fn); + if (TmpFd == nullptr) + delete Fd; + return nullptr; + } + if (ImmediateUnlink == false) + Fd->SetFileName(fn); + return Fd; +} + /*}}}*/ +bool Rename(std::string From, std::string To) /*{{{*/ +{ + if (rename(From.c_str(),To.c_str()) != 0) + { + _error->Error(_("rename failed, %s (%s -> %s)."),strerror(errno), + From.c_str(),To.c_str()); + return false; + } + return true; +} + /*}}}*/ +bool Popen(const char* Args[], FileFd &Fd, pid_t &Child, FileFd::OpenMode Mode)/*{{{*/ +{ + return Popen(Args, Fd, Child, Mode, true); +} + /*}}}*/ +bool Popen(const char* Args[], FileFd &Fd, pid_t &Child, FileFd::OpenMode Mode, bool CaptureStderr)/*{{{*/ +{ + return Popen(Args, Fd, Child, Mode, CaptureStderr, false); +} + /*}}}*/ +bool Popen(const char *Args[], FileFd &Fd, pid_t &Child, FileFd::OpenMode Mode, bool CaptureStderr, bool Sandbox) /*{{{*/ +{ + int fd; + if (Mode != FileFd::ReadOnly && Mode != FileFd::WriteOnly) + return _error->Error("Popen supports ReadOnly (x)or WriteOnly mode only"); + + int Pipe[2] = {-1, -1}; + if(pipe(Pipe) != 0) + return _error->Errno("pipe", _("Failed to create subprocess IPC")); + + std::set<int> keep_fds; + keep_fds.insert(Pipe[0]); + keep_fds.insert(Pipe[1]); + Child = ExecFork(keep_fds); + if(Child < 0) + return _error->Errno("fork", "Failed to fork"); + if(Child == 0) + { + if (Sandbox && (getuid() == 0 || geteuid() == 0) && !DropPrivileges()) + { + _error->DumpErrors(); + _exit(1); + } + if(Mode == FileFd::ReadOnly) + { + close(Pipe[0]); + fd = Pipe[1]; + } + else if(Mode == FileFd::WriteOnly) + { + close(Pipe[1]); + fd = Pipe[0]; + } + + if(Mode == FileFd::ReadOnly) + { + dup2(fd, 1); + if (CaptureStderr == true) + dup2(fd, 2); + } else if(Mode == FileFd::WriteOnly) + dup2(fd, 0); + + execv(Args[0], (char**)Args); + _exit(100); + } + if(Mode == FileFd::ReadOnly) + { + close(Pipe[1]); + fd = Pipe[0]; + } + else if(Mode == FileFd::WriteOnly) + { + close(Pipe[0]); + fd = Pipe[1]; + } + else + return _error->Error("Popen supports ReadOnly (x)or WriteOnly mode only"); + Fd.OpenDescriptor(fd, Mode, FileFd::None, true); + + return true; +} + /*}}}*/ +bool DropPrivileges() /*{{{*/ +{ + if(_config->FindB("Debug::NoDropPrivs", false) == true) + return true; + +#if __gnu_linux__ +#if defined(PR_SET_NO_NEW_PRIVS) && ( PR_SET_NO_NEW_PRIVS != 38 ) +#error "PR_SET_NO_NEW_PRIVS is defined, but with a different value than expected!" +#endif + // see prctl(2), needs linux3.5 at runtime - magic constant to avoid it at buildtime + int ret = prctl(38, 1, 0, 0, 0); + // ignore EINVAL - kernel is too old to understand the option + if(ret < 0 && errno != EINVAL) + _error->Warning("PR_SET_NO_NEW_PRIVS failed with %i", ret); +#endif + + // empty setting disables privilege dropping - this also ensures + // backward compatibility, see bug #764506 + const std::string toUser = _config->Find("APT::Sandbox::User"); + if (toUser.empty() || toUser == "root") + return true; + + // a lot can go wrong trying to drop privileges completely, + // so ideally we would like to verify that we have done it – + // but the verify asks for too much in case of fakeroot (and alike) + // [Specific checks can be overridden with dedicated options] + bool const VerifySandboxing = _config->FindB("APT::Sandbox::Verify", false); + + // uid will be 0 in the end, but gid might be different anyway + uid_t const old_uid = getuid(); + gid_t const old_gid = getgid(); + + if (old_uid != 0) + return true; + + struct passwd *pw = getpwnam(toUser.c_str()); + if (pw == NULL) + return _error->Error("No user %s, can not drop rights", toUser.c_str()); + + // Do not change the order here, it might break things + // Get rid of all our supplementary groups first + if (setgroups(1, &pw->pw_gid)) + return _error->Errno("setgroups", "Failed to setgroups"); + + // Now change the group ids to the new user +#ifdef HAVE_SETRESGID + if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) + return _error->Errno("setresgid", "Failed to set new group ids"); +#else + if (setegid(pw->pw_gid) != 0) + return _error->Errno("setegid", "Failed to setegid"); + + if (setgid(pw->pw_gid) != 0) + return _error->Errno("setgid", "Failed to setgid"); +#endif + + // Change the user ids to the new user +#ifdef HAVE_SETRESUID + if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) + return _error->Errno("setresuid", "Failed to set new user ids"); +#else + if (setuid(pw->pw_uid) != 0) + return _error->Errno("setuid", "Failed to setuid"); + if (seteuid(pw->pw_uid) != 0) + return _error->Errno("seteuid", "Failed to seteuid"); +#endif + + // disabled by default as fakeroot doesn't implement getgroups currently (#806521) + if (VerifySandboxing == true || _config->FindB("APT::Sandbox::Verify::Groups", false) == true) + { + // Verify that the user isn't still in any supplementary groups + long const ngroups_max = sysconf(_SC_NGROUPS_MAX); + std::unique_ptr<gid_t[]> gidlist(new gid_t[ngroups_max]); + if (unlikely(gidlist == NULL)) + return _error->Error("Allocation of a list of size %lu for getgroups failed", ngroups_max); + ssize_t gidlist_nr; + if ((gidlist_nr = getgroups(ngroups_max, gidlist.get())) < 0) + return _error->Errno("getgroups", "Could not get new groups (%lu)", ngroups_max); + for (ssize_t i = 0; i < gidlist_nr; ++i) + if (gidlist[i] != pw->pw_gid) + return _error->Error("Could not switch group, user %s is still in group %d", toUser.c_str(), gidlist[i]); + } + + // enabled by default as all fakeroot-lookalikes should fake that accordingly + if (VerifySandboxing == true || _config->FindB("APT::Sandbox::Verify::IDs", true) == true) + { + // Verify that gid, egid, uid, and euid changed + if (getgid() != pw->pw_gid) + return _error->Error("Could not switch group"); + if (getegid() != pw->pw_gid) + return _error->Error("Could not switch effective group"); + if (getuid() != pw->pw_uid) + return _error->Error("Could not switch user"); + if (geteuid() != pw->pw_uid) + return _error->Error("Could not switch effective user"); + +#ifdef HAVE_GETRESUID + // verify that the saved set-user-id was changed as well + uid_t ruid = 0; + uid_t euid = 0; + uid_t suid = 0; + if (getresuid(&ruid, &euid, &suid)) + return _error->Errno("getresuid", "Could not get saved set-user-ID"); + if (suid != pw->pw_uid) + return _error->Error("Could not switch saved set-user-ID"); +#endif + +#ifdef HAVE_GETRESGID + // verify that the saved set-group-id was changed as well + gid_t rgid = 0; + gid_t egid = 0; + gid_t sgid = 0; + if (getresgid(&rgid, &egid, &sgid)) + return _error->Errno("getresuid", "Could not get saved set-group-ID"); + if (sgid != pw->pw_gid) + return _error->Error("Could not switch saved set-group-ID"); +#endif + } + + // disabled as fakeroot doesn't forbid (by design) (re)gaining root from unprivileged + if (VerifySandboxing == true || _config->FindB("APT::Sandbox::Verify::Regain", false) == true) + { + // Check that uid and gid changes do not work anymore + if (pw->pw_gid != old_gid && (setgid(old_gid) != -1 || setegid(old_gid) != -1)) + return _error->Error("Could restore a gid to root, privilege dropping did not work"); + + if (pw->pw_uid != old_uid && (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) + return _error->Error("Could restore a uid to root, privilege dropping did not work"); + } + + if (_config->FindB("APT::Sandbox::ResetEnvironment", true)) + { + setenv("HOME", pw->pw_dir, 1); + setenv("USER", pw->pw_name, 1); + setenv("USERNAME", pw->pw_name, 1); + setenv("LOGNAME", pw->pw_name, 1); + auto const shell = flNotDir(pw->pw_shell); + if (shell == "false" || shell == "nologin") + setenv("SHELL", "/bin/sh", 1); + else + setenv("SHELL", pw->pw_shell, 1); + auto const apt_setenv_tmp = [](char const * const env) { + auto const tmpdir = getenv(env); + if (tmpdir != nullptr) + { + auto const ourtmpdir = GetTempDirEnv(env); + if (ourtmpdir != tmpdir) + setenv(env, ourtmpdir.c_str(), 1); + } + }; + apt_setenv_tmp("TMPDIR"); + apt_setenv_tmp("TEMPDIR"); + apt_setenv_tmp("TMP"); + apt_setenv_tmp("TEMP"); + } + + return true; +} + /*}}}*/ +bool OpenConfigurationFileFd(std::string const &File, FileFd &Fd) /*{{{*/ +{ + int const fd = open(File.c_str(), O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (fd == -1) + return _error->WarningE("open", _("Unable to read %s"), File.c_str()); + APT::Configuration::Compressor none(".", "", "", nullptr, nullptr, 0); + if (Fd.OpenDescriptor(fd, FileFd::ReadOnly, none, true) == false) + return false; + Fd.SetFileName(File); + return true; +} + /*}}}*/ +int Inhibit(const char *what, const char *who, const char *why, const char *mode) /*{{{*/ +{ +#ifdef HAVE_SYSTEMD + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *m = NULL; + sd_bus *bus = NULL; + int fd; + int r; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto out; + + r = sd_bus_call_method(bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "Inhibit", + &error, + &m, + "ssss", + what, + who, + why, + mode); + if (r < 0) + goto out; + + r = sd_bus_message_read(m, "h", &fd); + if (r < 0) + goto out; + + // We received a file descriptor, return it - systemd will close the read fd + // on free, so let's duplicate it here. + r = dup(fd); +out: + sd_bus_error_free(&error); + sd_bus_message_unref(m); + sd_bus_unref(bus); + return r; +#else + return -ENOTSUP; +#endif +} + /*}}}*/ diff --git a/apt-pkg/contrib/fileutl.h b/apt-pkg/contrib/fileutl.h new file mode 100644 index 0000000..9005b81 --- /dev/null +++ b/apt-pkg/contrib/fileutl.h @@ -0,0 +1,305 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + File Utilities + + CopyFile - Buffered copy of a single file + GetLock - dpkg compatible lock file manipulation (fcntl) + FileExists - Returns true if the file exists + SafeGetCWD - Returns the CWD in a string with overrun protection + + The file class is a handy abstraction for various functions+classes + that need to accept filenames. + + This source is placed in the Public Domain, do with it what you will + It was originally written by Jason Gunthorpe. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_FILEUTL_H +#define PKGLIB_FILEUTL_H + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/macros.h> + +#include <set> +#include <string> +#include <vector> +#include <time.h> + +#include <zlib.h> + +#ifndef APT_8_CLEANER_HEADERS +using std::string; +#endif + +/* Define this for python-apt */ +#define APT_HAS_GZIP 1 + +class FileFdPrivate; +class FileFd +{ + friend class FileFdPrivate; + friend class GzipFileFdPrivate; + friend class Bz2FileFdPrivate; + friend class LzmaFileFdPrivate; + friend class Lz4FileFdPrivate; + friend class ZstdFileFdPrivate; + friend class DirectFileFdPrivate; + friend class PipedFileFdPrivate; + protected: + int iFd; + + enum LocalFlags {AutoClose = (1<<0),Fail = (1<<1),DelOnFail = (1<<2), + HitEof = (1<<3), Replace = (1<<4), Compressed = (1<<5) }; + unsigned long Flags; + std::string FileName; + std::string TemporaryFileName; + + public: + enum OpenMode { + ReadOnly = (1 << 0), + WriteOnly = (1 << 1), + ReadWrite = ReadOnly | WriteOnly, + + Create = (1 << 2), + Exclusive = (1 << 3), + Atomic = Exclusive | (1 << 4), + Empty = (1 << 5), + BufferedWrite = (1 << 6), + + WriteEmpty = ReadWrite | Create | Empty, + WriteExists = ReadWrite, + WriteAny = ReadWrite | Create, + WriteTemp = ReadWrite | Create | Exclusive, + ReadOnlyGzip, + WriteAtomic = ReadWrite | Create | Atomic + }; + enum CompressMode + { + Auto = 'A', + None = 'N', + Extension = 'E', + Gzip = 'G', + Bzip2 = 'B', + Lzma = 'L', + Xz = 'X', + Lz4 = '4', + Zstd = 'Z' + }; + + inline bool Read(void *To,unsigned long long Size,bool AllowEof) + { + unsigned long long Jnk; + if (AllowEof) + return Read(To,Size,&Jnk); + return Read(To,Size); + } + bool Read(void *To,unsigned long long Size,unsigned long long *Actual = 0); + bool static Read(int const Fd, void *To, unsigned long long Size, unsigned long long * const Actual = 0); + /** read a complete line or until buffer is full + * + * The buffer will always be \\0 terminated, so at most Size-1 characters are read. + * If the buffer holds a complete line the last character (before \\0) will be + * the newline character \\n otherwise the line was longer than the buffer. + * + * @param To buffer which will hold the line + * @param Size of the buffer to fill + * @param \b nullptr is returned in error cases, otherwise + * the parameter \b To now filled with the line. + */ + char* ReadLine(char *To, unsigned long long const Size); + /** read a complete line from the file + * + * Similar to std::getline() the string does \b not include + * the newline, but just the content of the line as the newline + * is not needed to distinguish cases as for the other #ReadLine method. + * + * @param To string which will hold the line + * @return \b true if successful, otherwise \b false + */ + bool ReadLine(std::string &To); + bool Flush(); + bool Write(const void *From,unsigned long long Size); + bool static Write(int Fd, const void *From, unsigned long long Size); + bool Seek(unsigned long long To); + bool Skip(unsigned long long To); + bool Truncate(unsigned long long To); + unsigned long long Tell(); + // the size of the file content (compressed files will be uncompressed first) + unsigned long long Size(); + // the size of the file itself + unsigned long long FileSize(); + time_t ModificationTime(); + + /* You want to use 'unsigned long long' if you are talking about a file + to be able to support large files (>2 or >4 GB) properly. + This shouldn't happen all to often for the indexes, but deb's might be… + And as the auto-conversation converts a 'unsigned long *' to a 'bool' + instead of 'unsigned long long *' we need to provide this explicitly - + otherwise applications magically start to fail… */ + bool Read(void *To,unsigned long long Size,unsigned long *Actual) APT_DEPRECATED_MSG("The Actual variable you pass in should be an unsigned long long") + { + unsigned long long R; + bool const T = Read(To, Size, &R); + *Actual = R; + return T; + } + + bool Open(std::string FileName,unsigned int const Mode,CompressMode Compress,unsigned long const AccessMode = 0666); + bool Open(std::string FileName,unsigned int const Mode,APT::Configuration::Compressor const &compressor,unsigned long const AccessMode = 0666); + inline bool Open(std::string const &FileName,unsigned int const Mode, unsigned long const AccessMode = 0666) { + return Open(FileName, Mode, None, AccessMode); + }; + bool OpenDescriptor(int Fd, unsigned int const Mode, CompressMode Compress, bool AutoClose=false); + bool OpenDescriptor(int Fd, unsigned int const Mode, APT::Configuration::Compressor const &compressor, bool AutoClose=false); + inline bool OpenDescriptor(int Fd, unsigned int const Mode, bool AutoClose=false) { + return OpenDescriptor(Fd, Mode, None, AutoClose); + }; + bool Close(); + bool Sync(); + + // Simple manipulators + inline int Fd() {return iFd;}; + inline void Fd(int fd) { OpenDescriptor(fd, ReadWrite);}; + gzFile gzFd() APT_DEPRECATED_MSG("Implementation detail, do not use to be able to support bzip2, xz and co") APT_PURE; + + inline bool IsOpen() {return iFd >= 0;}; + inline bool Failed() {return (Flags & Fail) == Fail;}; + inline void EraseOnFailure() {Flags |= DelOnFail;}; + inline void OpFail() {Flags |= Fail;}; + inline bool Eof() {return (Flags & HitEof) == HitEof;}; + inline bool IsCompressed() {return (Flags & Compressed) == Compressed;}; + inline std::string &Name() {return FileName;}; + inline void SetFileName(std::string const &name) { FileName = name; }; + + FileFd(std::string FileName,unsigned int const Mode,unsigned long AccessMode = 0666); + FileFd(std::string FileName,unsigned int const Mode, CompressMode Compress, unsigned long AccessMode = 0666); + FileFd(); + FileFd(int const Fd, unsigned int const Mode = ReadWrite, CompressMode Compress = None); + FileFd(int const Fd, bool const AutoClose); + virtual ~FileFd(); + + private: + FileFdPrivate * d; + APT_HIDDEN FileFd(const FileFd &); + APT_HIDDEN FileFd & operator=(const FileFd &); + APT_HIDDEN bool OpenInternDescriptor(unsigned int const Mode, APT::Configuration::Compressor const &compressor); + + // private helpers to set Fail flag and call _error->Error + APT_HIDDEN bool FileFdErrno(const char* Function, const char* Description,...) APT_PRINTF(3) APT_COLD; + APT_HIDDEN bool FileFdError(const char* Description,...) APT_PRINTF(2) APT_COLD; +}; + +bool RunScripts(const char *Cnf); +bool CopyFile(FileFd &From,FileFd &To); +bool RemoveFile(char const * const Function, std::string const &FileName); +bool RemoveFileAt(char const * const Function, int const dirfd, std::string const &FileName); +int GetLock(std::string File,bool Errors = true); +bool FileExists(std::string File); +bool RealFileExists(std::string File); +bool DirectoryExists(std::string const &Path); +bool CreateDirectory(std::string const &Parent, std::string const &Path); +time_t GetModificationTime(std::string const &Path); +bool Rename(std::string From, std::string To); + +std::string GetTempDir(); +std::string GetTempDir(std::string const &User); +FileFd* GetTempFile(std::string const &Prefix = "", + bool ImmediateUnlink = true, + FileFd * const TmpFd = NULL); + +// FIXME: GetTempFile should always return a buffered file +FileFd* GetTempFile(std::string const &Prefix, + bool ImmediateUnlink , + FileFd * const TmpFd, + bool Buffered) APT_HIDDEN; + +/** \brief Ensure the existence of the given Path + * + * \param Parent directory of the Path directory - a trailing + * /apt/ will be removed before CreateDirectory call. + * \param Path which should exist after (successful) call + */ +bool CreateAPTDirectoryIfNeeded(std::string const &Parent, std::string const &Path); + +std::vector<std::string> GetListOfFilesInDir(std::string const &Dir, std::string const &Ext, + bool const &SortList, bool const &AllowNoExt=false); +std::vector<std::string> GetListOfFilesInDir(std::string const &Dir, std::vector<std::string> const &Ext, + bool const &SortList); +std::vector<std::string> GetListOfFilesInDir(std::string const &Dir, bool SortList); +std::string SafeGetCWD(); +void SetCloseExec(int Fd,bool Close); +void SetNonBlock(int Fd,bool Block); +bool WaitFd(int Fd,bool write = false,unsigned long timeout = 0); +pid_t ExecFork(); +pid_t ExecFork(std::set<int> keep_fds); +void MergeKeepFdsFromConfiguration(std::set<int> &keep_fds); +bool ExecWait(pid_t Pid,const char *Name,bool Reap = false); + +// check if the given file starts with a PGP cleartext signature +bool StartsWithGPGClearTextSignature(std::string const &FileName); + +/** change file attributes to requested known good values + * + * The method skips the user:group setting if not root. + * + * @param requester is printed as functionname in error cases + * @param file is the file to be modified + * @param user is the (new) owner of the file, e.g. _apt + * @param group is the (new) group owning the file, e.g. root + * @param mode is the access mode of the file, e.g. 0644 + */ +bool ChangeOwnerAndPermissionOfFile(char const * const requester, char const * const file, char const * const user, char const * const group, mode_t const mode); + +/** + * \brief Drop privileges + * + * Drop the privileges to the user _apt (or the one specified in + * APT::Sandbox::User). This does not set the supplementary group + * ids up correctly, it only uses the default group. Also prevent + * the process from gaining any new privileges afterwards, at least + * on Linux. + * + * \return true on success, false on failure with _error set + */ +bool DropPrivileges(); + +// File string manipulators +std::string flNotDir(std::string File); +std::string flNotFile(std::string File); +std::string flNoLink(std::string File); +std::string flExtension(std::string File); +std::string flCombine(std::string Dir,std::string File); + +/** \brief Takes a file path and returns the absolute path + */ +std::string flAbsPath(std::string File); +/** \brief removes superfluous /./ and // from path */ +APT_HIDDEN std::string flNormalize(std::string file); + +// simple c++ glob +std::vector<std::string> Glob(std::string const &pattern, int flags=0); + +/** \brief Popen() implementation that execv() instead of using a shell + * + * \param Args the execv style command to run + * \param FileFd is a reference to the FileFd to use for input or output + * \param Child a reference to the integer that stores the child pid + * Note that you must call ExecWait() or similar to cleanup + * \param Mode is either FileFd::ReadOnly or FileFd::WriteOnly + * \param CaptureStderr True if we should capture stderr in addition to stdout. + * (default: True). + * \param Sandbox True if this should run sandboxed + * \return true on success, false on failure with _error set + */ +bool Popen(const char *Args[], FileFd &Fd, pid_t &Child, FileFd::OpenMode Mode, bool CaptureStderr, bool Sandbox) APT_HIDDEN; +bool Popen(const char* Args[], FileFd &Fd, pid_t &Child, FileFd::OpenMode Mode, bool CaptureStderr); +bool Popen(const char* Args[], FileFd &Fd, pid_t &Child, FileFd::OpenMode Mode); + +APT_HIDDEN bool OpenConfigurationFileFd(std::string const &File, FileFd &Fd); + +APT_HIDDEN int Inhibit(const char *what, const char *who, const char *why, const char *mode); + +#endif diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc new file mode 100644 index 0000000..d956eaf --- /dev/null +++ b/apt-pkg/contrib/gpgv.cc @@ -0,0 +1,580 @@ +// -*- mode: cpp; mode: fold -*- +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/gpgv.h> +#include <apt-pkg/strutl.h> + +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <algorithm> +#include <fstream> +#include <iostream> +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +#include <apti18n.h> + /*}}}*/ + +// syntactic sugar to wrap a raw pointer with a custom deleter in a std::unique_ptr +static std::unique_ptr<char, decltype(&free)> make_unique_char(void *const str = nullptr) +{ + return {static_cast<char *>(str), &free}; +} +static std::unique_ptr<FILE, decltype(&fclose)> make_unique_FILE(std::string const &filename, char const *const mode) +{ + return {fopen(filename.c_str(), mode), &fclose}; +} + +class LineBuffer /*{{{*/ +{ + char *buffer = nullptr; + size_t buffer_size = 0; + int line_length = 0; + // a "normal" find_last_not_of returns npos if not found + int find_last_not_of_length(APT::StringView const bad) const + { + for (int result = line_length - 1; result >= 0; --result) + if (bad.find(buffer[result]) == APT::StringView::npos) + return result + 1; + return 0; + } + + public: + bool empty() const noexcept { return view().empty(); } + APT::StringView view() const noexcept { return {buffer, static_cast<size_t>(line_length)}; } + bool starts_with(APT::StringView const start) const { return view().substr(0, start.size()) == start; } + + bool writeTo(FileFd *const to, size_t offset = 0) const + { + if (to == nullptr) + return true; + return to->Write(buffer + offset, line_length - offset); + } + bool writeLineTo(FileFd *const to) const + { + if (to == nullptr) + return true; + buffer[line_length] = '\n'; + bool const result = to->Write(buffer, line_length + 1); + buffer[line_length] = '\0'; + return result; + } + bool writeNewLineIf(FileFd *const to, bool const condition) const + { + if (not condition || to == nullptr) + return true; + return to->Write("\n", 1); + } + + bool readFrom(FILE *stream, std::string const &InFile, bool acceptEoF = false) + { + errno = 0; + line_length = getline(&buffer, &buffer_size, stream); + if (errno != 0) + return _error->Errno("getline", "Could not read from %s", InFile.c_str()); + if (line_length == -1) + { + if (acceptEoF) + return false; + return _error->Error("Splitting of clearsigned file %s failed as it doesn't contain all expected parts", InFile.c_str()); + } + // a) remove newline characters, so we can work consistently with lines + line_length = find_last_not_of_length("\n\r"); + // b) remove trailing whitespaces as defined by rfc4880 §7.1 + line_length = find_last_not_of_length(" \t"); + buffer[line_length] = '\0'; + return true; + } + + ~LineBuffer() { free(buffer); } +}; +static bool operator==(LineBuffer const &buf, APT::StringView const exp) noexcept +{ + return buf.view() == exp; +} +static bool operator!=(LineBuffer const &buf, APT::StringView const exp) noexcept +{ + return buf.view() != exp; +} + /*}}}*/ +// ExecGPGV - returns the command needed for verify /*{{{*/ +// --------------------------------------------------------------------- +/* Generating the commandline for calling gpg is somehow complicated as + we need to add multiple keyrings and user supplied options. + Also, as gpg has no options to enforce a certain reduced style of + clear-signed files (=the complete content of the file is signed and + the content isn't encoded) we do a divide and conquer approach here + and split up the clear-signed file in message and signature for gpg. + And as a cherry on the cake, we use our apt-key wrapper to do part + of the lifting in regards to merging keyrings. Fun for the whole family. +*/ +static bool iovprintf(std::ostream &out, const char *format, + va_list &args, ssize_t &size) { + auto S = make_unique_char(malloc(size)); + ssize_t const n = vsnprintf(S.get(), size, format, args); + if (n > -1 && n < size) { + out << S.get(); + return true; + } else { + if (n > -1) + size = n + 1; + else + size *= 2; + } + return false; +} +static void APT_PRINTF(4) apt_error(std::ostream &outterm, int const statusfd, int fd[2], const char *format, ...) +{ + std::ostringstream outstr; + std::ostream &out = (statusfd == -1) ? outterm : outstr; + va_list args; + ssize_t size = 400; + while (true) { + bool ret; + va_start(args,format); + ret = iovprintf(out, format, args, size); + va_end(args); + if (ret) + break; + } + if (statusfd != -1) + { + auto const errtag = "[APTKEY:] ERROR "; + outstr << '\n'; + auto const errtext = outstr.str(); + if (not FileFd::Write(fd[1], errtag, strlen(errtag)) || + not FileFd::Write(fd[1], errtext.data(), errtext.size())) + outterm << errtext << std::flush; + } +} +void ExecGPGV(std::string const &File, std::string const &FileGPG, + int const &statusfd, int fd[2], std::string const &key) +{ + #define EINTERNAL 111 + std::string const aptkey = _config->Find("Dir::Bin::apt-key", CMAKE_INSTALL_FULL_BINDIR "/apt-key"); + + bool const Debug = _config->FindB("Debug::Acquire::gpgv", false); + struct exiter { + std::vector<const char *> files; + void operator ()(int code) APT_NORETURN { + std::for_each(files.begin(), files.end(), unlink); + exit(code); + } + } local_exit; + + + std::vector<const char *> Args; + Args.reserve(10); + + Args.push_back(aptkey.c_str()); + Args.push_back("--quiet"); + Args.push_back("--readonly"); + auto const keysFileFpr = VectorizeString(key, ','); + for (auto const &k: keysFileFpr) + { + if (unlikely(k.empty())) + continue; + if (k[0] == '/') + { + Args.push_back("--keyring"); + Args.push_back(k.c_str()); + } + else + { + Args.push_back("--keyid"); + Args.push_back(k.c_str()); + } + } + Args.push_back("verify"); + + char statusfdstr[10]; + if (statusfd != -1) + { + Args.push_back("--status-fd"); + snprintf(statusfdstr, sizeof(statusfdstr), "%i", statusfd); + Args.push_back(statusfdstr); + } + + Configuration::Item const *Opts; + Opts = _config->Tree("Acquire::gpgv::Options"); + if (Opts != 0) + { + Opts = Opts->Child; + for (; Opts != 0; Opts = Opts->Next) + { + if (Opts->Value.empty()) + continue; + Args.push_back(Opts->Value.c_str()); + } + } + + enum { DETACHED, CLEARSIGNED } releaseSignature = (FileGPG != File) ? DETACHED : CLEARSIGNED; + auto sig = make_unique_char(); + auto data = make_unique_char(); + auto conf = make_unique_char(); + + // Dump the configuration so apt-key picks up the correct Dir values + { + { + std::string tmpfile; + strprintf(tmpfile, "%s/apt.conf.XXXXXX", GetTempDir().c_str()); + conf.reset(strdup(tmpfile.c_str())); + } + if (conf == nullptr) { + apt_error(std::cerr, statusfd, fd, "Couldn't create tempfile names for passing config to apt-key"); + local_exit(EINTERNAL); + } + int confFd = mkstemp(conf.get()); + if (confFd == -1) { + apt_error(std::cerr, statusfd, fd, "Couldn't create temporary file %s for passing config to apt-key", conf.get()); + local_exit(EINTERNAL); + } + local_exit.files.push_back(conf.get()); + + std::ofstream confStream(conf.get()); + close(confFd); + _config->Dump(confStream); + confStream.close(); + setenv("APT_CONFIG", conf.get(), 1); + } + + if (releaseSignature == DETACHED) + { + auto detached = make_unique_FILE(FileGPG, "r"); + if (detached.get() == nullptr) + { + apt_error(std::cerr, statusfd, fd, "Detached signature file '%s' could not be opened", FileGPG.c_str()); + local_exit(EINTERNAL); + } + LineBuffer buf; + bool open_signature = false; + bool found_badcontent = false; + size_t found_signatures = 0; + while (buf.readFrom(detached.get(), FileGPG, true)) + { + if (open_signature) + { + if (buf == "-----END PGP SIGNATURE-----") + open_signature = false; + else if (buf.starts_with("-")) + { + // the used Radix-64 is not using dash for any value, so a valid line can't + // start with one. Header keys could, but no existent one does and seems unlikely. + // Instead it smells a lot like a header the parser didn't recognize. + apt_error(std::cerr, statusfd, fd, "Detached signature file '%s' contains unexpected line starting with a dash", FileGPG.c_str()); + local_exit(112); + } + } + else //if (not open_signature) + { + if (buf == "-----BEGIN PGP SIGNATURE-----") + { + open_signature = true; + ++found_signatures; + if (found_badcontent) + break; + } + else + { + found_badcontent = true; + if (found_signatures != 0) + break; + } + } + } + if (found_signatures == 0 && statusfd != -1) + { + auto const errtag = "[GNUPG:] NODATA\n"; + FileFd::Write(fd[1], errtag, strlen(errtag)); + // guess if this is a binary signature, we never officially supported them, + // but silently accepted them via passing them unchecked to gpgv + if (found_badcontent) + { + rewind(detached.get()); + auto ptag = fgetc(detached.get()); + // §4.2 says that the first bit is always set and gpg seems to generate + // only old format which is indicated by the second bit not set + if (ptag != EOF && (ptag & 0x80) != 0 && (ptag & 0x40) == 0) + { + apt_error(std::cerr, statusfd, fd, "Detached signature file '%s' is in unsupported binary format", FileGPG.c_str()); + local_exit(112); + } + } + // This is not an attack attempt but a file even gpgv would complain about + // likely the result of a paywall which is covered by the gpgv method + local_exit(113); + } + else if (found_badcontent) + { + apt_error(std::cerr, statusfd, fd, "Detached signature file '%s' contains lines not belonging to a signature", FileGPG.c_str()); + local_exit(112); + } + if (open_signature) + { + apt_error(std::cerr, statusfd, fd, "Detached signature file '%s' contains unclosed signatures", FileGPG.c_str()); + local_exit(112); + } + + Args.push_back(FileGPG.c_str()); + Args.push_back(File.c_str()); + } + else // clear-signed file + { + FileFd signature; + if (GetTempFile("apt.sig", false, &signature) == nullptr) + local_exit(EINTERNAL); + sig.reset(strdup(signature.Name().c_str())); + local_exit.files.push_back(sig.get()); + FileFd message; + if (GetTempFile("apt.data", false, &message) == nullptr) + local_exit(EINTERNAL); + data.reset(strdup(message.Name().c_str())); + local_exit.files.push_back(data.get()); + + if (signature.Failed() || message.Failed() || + not SplitClearSignedFile(File, &message, nullptr, &signature)) + { + apt_error(std::cerr, statusfd, fd, "Splitting up %s into data and signature failed", File.c_str()); + local_exit(112); + } + Args.push_back(sig.get()); + Args.push_back(data.get()); + } + + Args.push_back(NULL); + + if (Debug) + { + std::clog << "Preparing to exec: "; + for (std::vector<const char *>::const_iterator a = Args.begin(); *a != NULL; ++a) + std::clog << " " << *a; + std::clog << std::endl; + } + + if (statusfd != -1) + { + int const nullfd = open("/dev/null", O_WRONLY); + close(fd[0]); + // Redirect output to /dev/null; we read from the status fd + if (statusfd != STDOUT_FILENO) + dup2(nullfd, STDOUT_FILENO); + if (statusfd != STDERR_FILENO) + dup2(nullfd, STDERR_FILENO); + // Redirect the pipe to the status fd (3) + dup2(fd[1], statusfd); + + putenv((char *)"LANG="); + putenv((char *)"LC_ALL="); + putenv((char *)"LC_MESSAGES="); + } + + + // We have created tempfiles we have to clean up + // and we do an additional check, so fork yet another time … + pid_t pid = ExecFork(); + if(pid < 0) { + apt_error(std::cerr, statusfd, fd, "Fork failed for %s to check %s", Args[0], File.c_str()); + local_exit(EINTERNAL); + } + if(pid == 0) + { + if (statusfd != -1) + dup2(fd[1], statusfd); + execvp(Args[0], (char **) &Args[0]); + apt_error(std::cerr, statusfd, fd, "Couldn't execute %s to check %s", Args[0], File.c_str()); + local_exit(EINTERNAL); + } + + // Wait and collect the error code - taken from WaitPid as we need the exact Status + int Status; + while (waitpid(pid,&Status,0) != pid) + { + if (errno == EINTR) + continue; + apt_error(std::cerr, statusfd, fd, _("Waited for %s but it wasn't there"), "apt-key"); + local_exit(EINTERNAL); + } + + // check if it exit'ed normally … + if (not WIFEXITED(Status)) + { + apt_error(std::cerr, statusfd, fd, _("Sub-process %s exited unexpectedly"), "apt-key"); + local_exit(EINTERNAL); + } + + // … and with a good exit code + if (WEXITSTATUS(Status) != 0) + { + // we forward the statuscode, so don't generate a message on the fd in this case + apt_error(std::cerr, -1, fd, _("Sub-process %s returned an error code (%u)"), "apt-key", WEXITSTATUS(Status)); + local_exit(WEXITSTATUS(Status)); + } + + // everything fine + local_exit(0); +} + /*}}}*/ +// SplitClearSignedFile - split message into data/signature /*{{{*/ +bool SplitClearSignedFile(std::string const &InFile, FileFd * const ContentFile, + std::vector<std::string> * const ContentHeader, FileFd * const SignatureFile) +{ + auto in = make_unique_FILE(InFile, "r"); + if (in.get() == nullptr) + return _error->Errno("fopen", "can not open %s", InFile.c_str()); + + struct ScopedErrors + { + ScopedErrors() { _error->PushToStack(); } + ~ScopedErrors() { _error->MergeWithStack(); } + } scoped; + LineBuffer buf; + + // start of the message + if (not buf.readFrom(in.get(), InFile)) + return false; // empty or read error + if (buf != "-----BEGIN PGP SIGNED MESSAGE-----") + { + // this might be an unsigned file we don't want to report errors for, + // but still finish unsuccessful none the less. + while (buf.readFrom(in.get(), InFile, true)) + if (buf == "-----BEGIN PGP SIGNED MESSAGE-----") + return _error->Error("Clearsigned file '%s' does not start with a signed message block.", InFile.c_str()); + + return false; + } + + // save "Hash" Armor Headers + while (true) + { + if (not buf.readFrom(in.get(), InFile)) + return false; + if (buf.empty()) + break; // empty line ends the Armor Headers + if (buf.starts_with("-")) + // § 6.2 says unknown keys should be reported to the user. We don't go that far, + // but we assume that there will never be a header key starting with a dash + return _error->Error("Clearsigned file '%s' contains unexpected line starting with a dash (%s)", InFile.c_str(), "armor"); + if (ContentHeader != nullptr && buf.starts_with("Hash: ")) + ContentHeader->push_back(buf.view().to_string()); + } + + // the message itself + bool first_line = true; + while (true) + { + if (not buf.readFrom(in.get(), InFile)) + return false; + + if (buf.starts_with("-")) + { + if (buf == "-----BEGIN PGP SIGNATURE-----") + { + if (not buf.writeLineTo(SignatureFile)) + return false; + break; + } + else if (buf.starts_with("- ")) + { + // we don't have any fields which need to be dash-escaped, + // but implementations are free to escape all lines … + if (not buf.writeNewLineIf(ContentFile, not first_line) || not buf.writeTo(ContentFile, 2)) + return false; + } + else + // § 7.1 says a client should warn, but we don't really work with files which + // should contain lines starting with a dash, so it is a lot more likely that + // this is an attempt to trick our parser vs. gpgv parser into ignoring a header + return _error->Error("Clearsigned file '%s' contains unexpected line starting with a dash (%s)", InFile.c_str(), "msg"); + } + else if (not buf.writeNewLineIf(ContentFile, not first_line) || not buf.writeTo(ContentFile)) + return false; + first_line = false; + } + + // collect all signatures + bool open_signature = true; + while (true) + { + if (not buf.readFrom(in.get(), InFile, true)) + break; + + if (open_signature) + { + if (buf == "-----END PGP SIGNATURE-----") + open_signature = false; + else if (buf.starts_with("-")) + // the used Radix-64 is not using dash for any value, so a valid line can't + // start with one. Header keys could, but no existent one does and seems unlikely. + // Instead it smells a lot like a header the parser didn't recognize. + return _error->Error("Clearsigned file '%s' contains unexpected line starting with a dash (%s)", InFile.c_str(), "sig"); + } + else //if (not open_signature) + { + if (buf == "-----BEGIN PGP SIGNATURE-----") + open_signature = true; + else + return _error->Error("Clearsigned file '%s' contains unsigned lines.", InFile.c_str()); + } + + if (not buf.writeLineTo(SignatureFile)) + return false; + } + if (open_signature) + return _error->Error("Signature in file %s wasn't closed", InFile.c_str()); + + // Flush the files + if (SignatureFile != nullptr) + SignatureFile->Flush(); + if (ContentFile != nullptr) + ContentFile->Flush(); + + // Catch-all for "unhandled" read/sync errors + if (_error->PendingError()) + return false; + return true; +} + /*}}}*/ +bool OpenMaybeClearSignedFile(std::string const &ClearSignedFileName, FileFd &MessageFile) /*{{{*/ +{ + // Buffered file + if (GetTempFile("clearsigned.message", true, &MessageFile, true) == nullptr) + return false; + if (MessageFile.Failed()) + return _error->Error("Couldn't open temporary file to work with %s", ClearSignedFileName.c_str()); + + _error->PushToStack(); + bool const splitDone = SplitClearSignedFile(ClearSignedFileName, &MessageFile, NULL, NULL); + bool const errorDone = _error->PendingError(); + _error->MergeWithStack(); + if (not splitDone) + { + MessageFile.Close(); + + if (errorDone) + return false; + + // we deal with an unsigned file + MessageFile.Open(ClearSignedFileName, FileFd::ReadOnly); + } + else // clear-signed + { + if (not MessageFile.Seek(0)) + return _error->Errno("lseek", "Unable to seek back in message for file %s", ClearSignedFileName.c_str()); + } + + return not MessageFile.Failed(); +} + /*}}}*/ diff --git a/apt-pkg/contrib/gpgv.h b/apt-pkg/contrib/gpgv.h new file mode 100644 index 0000000..2a4cdad --- /dev/null +++ b/apt-pkg/contrib/gpgv.h @@ -0,0 +1,92 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Helpers to deal with gpgv better and more easily + + ##################################################################### */ + /*}}}*/ +#ifndef CONTRIB_GPGV_H +#define CONTRIB_GPGV_H + +#include <apt-pkg/macros.h> + +#include <string> +#include <vector> + +#ifndef APT_10_CLEANER_HEADERS +#include <apt-pkg/fileutl.h> +#endif + +class FileFd; + +/** \brief generates and run the command to verify a file with gpgv + * + * If File and FileSig specify the same file it is assumed that we + * deal with a clear-signed message. Note that the method will accept + * and validate files which include additional (unsigned) messages + * without complaining. Do NOT open files accepted by this method + * for reading. Use #OpenMaybeClearSignedFile to access the message + * instead to ensure you are only reading signed data. + * + * The method does not return, but has some notable exit-codes: + * 111 signals an internal error like the inability to execute gpgv, + * 112 indicates a clear-signed file which doesn't include a message, + * which can happen if APT is run while on a network requiring + * authentication before usage (e.g. in hotels) + * All other exit-codes are passed-through from gpgv. + * + * @param File is the message (unsigned or clear-signed) + * @param FileSig is the signature (detached or clear-signed) + * @param statusfd is the fd given to gpgv as --status-fd + * @param fd is used as a pipe for the standard output of gpgv + * @param key is the specific one to be used instead of using all + */ +void ExecGPGV(std::string const &File, std::string const &FileSig, + int const &statusfd, int fd[2], std::string const &Key = "") APT_NORETURN; +inline APT_NORETURN void ExecGPGV(std::string const &File, std::string const &FileSig, + int const &statusfd = -1) { + int fd[2]; + ExecGPGV(File, FileSig, statusfd, fd); +} + +/** \brief Split an inline signature into message and signature + * + * Takes a clear-signed message and puts the first signed message + * in the content file and all signatures following it into the + * second. Unsigned messages, additional messages as well as + * whitespaces are discarded. The resulting files are suitable to + * be checked with gpgv. + * + * If a FileFd pointers is NULL it will not be used and the content + * which would have been written to it is silently discarded. + * + * The content of the split files is undefined if the splitting was + * unsuccessful. + * + * Note that trying to split an unsigned file will fail, but + * not generate an error message. + * + * @param InFile is the clear-signed file + * @param ContentFile is the FileFd the message will be written to + * @param ContentHeader is a list of all required Amored Headers for the message + * @param SignatureFile is the FileFd all signatures will be written to + * @return true if the splitting was successful, false otherwise + */ +bool SplitClearSignedFile(std::string const &InFile, FileFd * const ContentFile, + std::vector<std::string> * const ContentHeader, FileFd * const SignatureFile); + +/** \brief open a file which might be clear-signed + * + * This method tries to extract the (signed) message of a file. + * If the file isn't signed it will just open the given filename. + * Otherwise the message is extracted to a temporary file which + * will be opened instead. + * + * @param ClearSignedFileName is the name of the file to open + * @param[out] MessageFile is the FileFd in which the file will be opened + * @return true if opening was successful, otherwise false + */ +bool OpenMaybeClearSignedFile(std::string const &ClearSignedFileName, FileFd &MessageFile); + +#endif diff --git a/apt-pkg/contrib/hashes.cc b/apt-pkg/contrib/hashes.cc new file mode 100644 index 0000000..98b92cc --- /dev/null +++ b/apt-pkg/contrib/hashes.cc @@ -0,0 +1,408 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Hashes - Simple wrapper around the hash functions + + This is just used to make building the methods simpler, this is the + only interface required.. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/md5.h> +#include <apt-pkg/sha1.h> +#include <apt-pkg/sha2.h> + +#include <algorithm> +#include <iostream> +#include <string> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> + /*}}}*/ + +const char * HashString::_SupportedHashes[] = +{ + "SHA512", "SHA256", "SHA1", "MD5Sum", "Checksum-FileSize", NULL +}; + +HashString::HashString() +{ +} + +HashString::HashString(std::string Type, std::string Hash) : Type(Type), Hash(Hash) +{ +} + +HashString::HashString(std::string StringedHash) /*{{{*/ +{ + if (StringedHash.find(":") == std::string::npos) + { + // legacy: md5sum without "MD5Sum:" prefix + if (StringedHash.size() == 32) + { + Type = "MD5Sum"; + Hash = StringedHash; + } + if(_config->FindB("Debug::Hashes",false) == true) + std::clog << "HashString(string): invalid StringedHash " << StringedHash << std::endl; + return; + } + std::string::size_type pos = StringedHash.find(":"); + Type = StringedHash.substr(0,pos); + Hash = StringedHash.substr(pos+1, StringedHash.size() - pos); + + if(_config->FindB("Debug::Hashes",false) == true) + std::clog << "HashString(string): " << Type << " : " << Hash << std::endl; +} + /*}}}*/ +bool HashString::VerifyFile(std::string filename) const /*{{{*/ +{ + std::string fileHash = GetHashForFile(filename); + + if(_config->FindB("Debug::Hashes",false) == true) + std::clog << "HashString::VerifyFile: got: " << fileHash << " expected: " << toStr() << std::endl; + + return (fileHash == Hash); +} + /*}}}*/ +bool HashString::FromFile(std::string filename) /*{{{*/ +{ + // pick the strongest hash + if (Type == "") + Type = _SupportedHashes[0]; + + Hash = GetHashForFile(filename); + return true; +} + /*}}}*/ +std::string HashString::GetHashForFile(std::string filename) const /*{{{*/ +{ + std::string fileHash; + + FileFd Fd(filename, FileFd::ReadOnly); + if(strcasecmp(Type.c_str(), "MD5Sum") == 0) + { + MD5Summation MD5; + MD5.AddFD(Fd); + fileHash = (std::string)MD5.Result(); + } + else if (strcasecmp(Type.c_str(), "SHA1") == 0) + { + SHA1Summation SHA1; + SHA1.AddFD(Fd); + fileHash = (std::string)SHA1.Result(); + } + else if (strcasecmp(Type.c_str(), "SHA256") == 0) + { + SHA256Summation SHA256; + SHA256.AddFD(Fd); + fileHash = (std::string)SHA256.Result(); + } + else if (strcasecmp(Type.c_str(), "SHA512") == 0) + { + SHA512Summation SHA512; + SHA512.AddFD(Fd); + fileHash = (std::string)SHA512.Result(); + } + else if (strcasecmp(Type.c_str(), "Checksum-FileSize") == 0) + strprintf(fileHash, "%llu", Fd.FileSize()); + Fd.Close(); + + return fileHash; +} + /*}}}*/ +const char** HashString::SupportedHashes() /*{{{*/ +{ + return _SupportedHashes; +} + /*}}}*/ +APT_PURE bool HashString::empty() const /*{{{*/ +{ + return (Type.empty() || Hash.empty()); +} + /*}}}*/ + +APT_PURE static bool IsConfigured(const char *name, const char *what) +{ + std::string option; + strprintf(option, "APT::Hashes::%s::%s", name, what); + return _config->FindB(option, false); +} + +APT_PURE bool HashString::usable() const /*{{{*/ +{ + return ( + (Type != "Checksum-FileSize") && + (Type != "MD5Sum") && + (Type != "SHA1") && + !IsConfigured(Type.c_str(), "Untrusted") + ); +} + /*}}}*/ +std::string HashString::toStr() const /*{{{*/ +{ + return Type + ":" + Hash; +} + /*}}}*/ +APT_PURE bool HashString::operator==(HashString const &other) const /*{{{*/ +{ + return (strcasecmp(Type.c_str(), other.Type.c_str()) == 0 && Hash == other.Hash); +} +APT_PURE bool HashString::operator!=(HashString const &other) const +{ + return !(*this == other); +} + /*}}}*/ + +bool HashStringList::usable() const /*{{{*/ +{ + if (empty() == true) + return false; + std::string const forcedType = _config->Find("Acquire::ForceHash", ""); + if (forcedType.empty() == true) + { + // See if there is at least one usable hash + for (auto const &hs: list) + if (hs.usable()) + return true; + return false; + } + return find(forcedType) != NULL; +} + /*}}}*/ +HashString const * HashStringList::find(char const * const type) const /*{{{*/ +{ + if (type == NULL || type[0] == '\0') + { + std::string const forcedType = _config->Find("Acquire::ForceHash", ""); + if (forcedType.empty() == false) + return find(forcedType.c_str()); + for (char const * const * t = HashString::SupportedHashes(); *t != NULL; ++t) + for (std::vector<HashString>::const_iterator hs = list.begin(); hs != list.end(); ++hs) + if (strcasecmp(hs->HashType().c_str(), *t) == 0) + return &*hs; + return NULL; + } + for (std::vector<HashString>::const_iterator hs = list.begin(); hs != list.end(); ++hs) + if (strcasecmp(hs->HashType().c_str(), type) == 0) + return &*hs; + return NULL; +} + /*}}}*/ +unsigned long long HashStringList::FileSize() const /*{{{*/ +{ + HashString const * const hsf = find("Checksum-FileSize"); + if (hsf == NULL) + return 0; + std::string const hv = hsf->HashValue(); + return strtoull(hv.c_str(), NULL, 10); +} + /*}}}*/ +bool HashStringList::FileSize(unsigned long long const Size) /*{{{*/ +{ + return push_back(HashString("Checksum-FileSize", std::to_string(Size))); +} + /*}}}*/ +bool HashStringList::supported(char const * const type) /*{{{*/ +{ + for (char const * const * t = HashString::SupportedHashes(); *t != NULL; ++t) + if (strcasecmp(*t, type) == 0) + return true; + return false; +} + /*}}}*/ +bool HashStringList::push_back(const HashString &hashString) /*{{{*/ +{ + if (hashString.HashType().empty() == true || + hashString.HashValue().empty() == true || + supported(hashString.HashType().c_str()) == false) + return false; + + // ensure that each type is added only once + HashString const * const hs = find(hashString.HashType().c_str()); + if (hs != NULL) + return *hs == hashString; + + list.push_back(hashString); + return true; +} + /*}}}*/ +bool HashStringList::VerifyFile(std::string filename) const /*{{{*/ +{ + if (usable() == false) + return false; + + Hashes hashes(*this); + FileFd file(filename, FileFd::ReadOnly); + HashString const * const hsf = find("Checksum-FileSize"); + if (hsf != NULL) + { + std::string fileSize; + strprintf(fileSize, "%llu", file.FileSize()); + if (hsf->HashValue() != fileSize) + return false; + } + hashes.AddFD(file); + HashStringList const hsl = hashes.GetHashStringList(); + return hsl == *this; +} + /*}}}*/ +bool HashStringList::operator==(HashStringList const &other) const /*{{{*/ +{ + std::string const forcedType = _config->Find("Acquire::ForceHash", ""); + if (forcedType.empty() == false) + { + HashString const * const hs = find(forcedType); + HashString const * const ohs = other.find(forcedType); + if (hs == NULL || ohs == NULL) + return false; + return *hs == *ohs; + } + short matches = 0; + for (const_iterator hs = begin(); hs != end(); ++hs) + { + HashString const * const ohs = other.find(hs->HashType()); + if (ohs == NULL) + continue; + if (*hs != *ohs) + return false; + ++matches; + } + if (matches == 0) + return false; + return true; +} +bool HashStringList::operator!=(HashStringList const &other) const +{ + return !(*this == other); +} + /*}}}*/ + +// PrivateHashes /*{{{*/ +class PrivateHashes { +public: + unsigned long long FileSize; + unsigned int CalcHashes; + + explicit PrivateHashes(unsigned int const CalcHashes) : FileSize(0), CalcHashes(CalcHashes) {} + explicit PrivateHashes(HashStringList const &Hashes) : FileSize(0) { + unsigned int calcHashes = Hashes.usable() ? 0 : ~0; + if (Hashes.find("MD5Sum") != NULL) + calcHashes |= Hashes::MD5SUM; + if (Hashes.find("SHA1") != NULL) + calcHashes |= Hashes::SHA1SUM; + if (Hashes.find("SHA256") != NULL) + calcHashes |= Hashes::SHA256SUM; + if (Hashes.find("SHA512") != NULL) + calcHashes |= Hashes::SHA512SUM; + CalcHashes = calcHashes; + } +}; + /*}}}*/ +// Hashes::Add* - Add the contents of data or FD /*{{{*/ +bool Hashes::Add(const unsigned char * const Data, unsigned long long const Size) +{ + if (Size == 0) + return true; + bool Res = true; +APT_IGNORE_DEPRECATED_PUSH + if ((d->CalcHashes & MD5SUM) == MD5SUM) + Res &= MD5.Add(Data, Size); + if ((d->CalcHashes & SHA1SUM) == SHA1SUM) + Res &= SHA1.Add(Data, Size); + if ((d->CalcHashes & SHA256SUM) == SHA256SUM) + Res &= SHA256.Add(Data, Size); + if ((d->CalcHashes & SHA512SUM) == SHA512SUM) + Res &= SHA512.Add(Data, Size); +APT_IGNORE_DEPRECATED_POP + d->FileSize += Size; + return Res; +} +bool Hashes::Add(const unsigned char * const Data, unsigned long long const Size, unsigned int const Hashes) +{ + d->CalcHashes = Hashes; + return Add(Data, Size); +} +bool Hashes::AddFD(int const Fd,unsigned long long Size) +{ + unsigned char Buf[64*64]; + bool const ToEOF = (Size == UntilEOF); + while (Size != 0 || ToEOF) + { + decltype(Size) n = sizeof(Buf); + if (!ToEOF) n = std::min(Size, n); + ssize_t const Res = read(Fd,Buf,n); + if (Res < 0 || (!ToEOF && Res != (ssize_t) n)) // error, or short read + return false; + if (ToEOF && Res == 0) // EOF + break; + Size -= Res; + if (Add(Buf, Res) == false) + return false; + } + return true; +} +bool Hashes::AddFD(int const Fd,unsigned long long Size, unsigned int const Hashes) +{ + d->CalcHashes = Hashes; + return AddFD(Fd, Size); +} +bool Hashes::AddFD(FileFd &Fd,unsigned long long Size) +{ + unsigned char Buf[64*64]; + bool const ToEOF = (Size == 0); + while (Size != 0 || ToEOF) + { + decltype(Size) n = sizeof(Buf); + if (!ToEOF) n = std::min(Size, n); + decltype(Size) a = 0; + if (Fd.Read(Buf, n, &a) == false) // error + return false; + if (ToEOF == false) + { + if (a != n) // short read + return false; + } + else if (a == 0) // EOF + break; + Size -= a; + if (Add(Buf, a) == false) + return false; + } + return true; +} +bool Hashes::AddFD(FileFd &Fd,unsigned long long Size, unsigned int const Hashes) +{ + d->CalcHashes = Hashes; + return AddFD(Fd, Size); +} + /*}}}*/ +HashStringList Hashes::GetHashStringList() +{ + HashStringList hashes; +APT_IGNORE_DEPRECATED_PUSH + if ((d->CalcHashes & MD5SUM) == MD5SUM) + hashes.push_back(HashString("MD5Sum", MD5.Result().Value())); + if ((d->CalcHashes & SHA1SUM) == SHA1SUM) + hashes.push_back(HashString("SHA1", SHA1.Result().Value())); + if ((d->CalcHashes & SHA256SUM) == SHA256SUM) + hashes.push_back(HashString("SHA256", SHA256.Result().Value())); + if ((d->CalcHashes & SHA512SUM) == SHA512SUM) + hashes.push_back(HashString("SHA512", SHA512.Result().Value())); +APT_IGNORE_DEPRECATED_POP + hashes.FileSize(d->FileSize); + return hashes; +} +APT_IGNORE_DEPRECATED_PUSH +Hashes::Hashes() : d(new PrivateHashes(~0)) { } +Hashes::Hashes(unsigned int const Hashes) : d(new PrivateHashes(Hashes)) {} +Hashes::Hashes(HashStringList const &Hashes) : d(new PrivateHashes(Hashes)) {} +Hashes::~Hashes() { delete d; } +APT_IGNORE_DEPRECATED_POP diff --git a/apt-pkg/contrib/hashes.h b/apt-pkg/contrib/hashes.h new file mode 100644 index 0000000..28bfd34 --- /dev/null +++ b/apt-pkg/contrib/hashes.h @@ -0,0 +1,250 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Hashes - Simple wrapper around the hash functions + + This is just used to make building the methods simpler, this is the + only interface required.. + + ##################################################################### */ + /*}}}*/ +#ifndef APTPKG_HASHES_H +#define APTPKG_HASHES_H + +#include <apt-pkg/macros.h> +#include <apt-pkg/md5.h> +#include <apt-pkg/sha1.h> +#include <apt-pkg/sha2.h> + +#include <cstring> +#include <string> + +#ifndef APT_8_CLEANER_HEADERS +using std::min; +using std::vector; +#endif +#ifndef APT_10_CLEANER_HEADERS +#include <apt-pkg/fileutl.h> +#include <algorithm> +#include <vector> +#endif + + +class FileFd; + +// helper class that contains hash function name +// and hash +class HashString +{ + protected: + std::string Type; + std::string Hash; + static const char * _SupportedHashes[10]; + + // internal helper + std::string GetHashForFile(std::string filename) const; + + public: + HashString(std::string Type, std::string Hash); + HashString(std::string StringedHashString); // init from str as "type:hash" + HashString(); + + // get hash type used + std::string HashType() const { return Type; }; + std::string HashValue() const { return Hash; }; + APT_DEPRECATED_MSG("method was const-ified") std::string HashType() { return Type; }; + APT_DEPRECATED_MSG("method was const-ified") std::string HashValue() { return Hash; }; + + // verify the given filename against the currently loaded hash + bool VerifyFile(std::string filename) const; + + // generate a hash string from the given filename + bool FromFile(std::string filename); + + + // helper + std::string toStr() const; // convert to str as "type:hash" + bool empty() const; + bool usable() const; + bool operator==(HashString const &other) const; + bool operator!=(HashString const &other) const; + + // return the list of hashes we support + static APT_PURE const char** SupportedHashes(); +}; + +class HashStringList +{ + public: + /** find best hash if no specific one is requested + * + * @param type of the checksum to return, can be \b NULL + * @return If type is \b NULL (or the empty string) it will + * return the 'best' hash; otherwise the hash which was + * specifically requested. If no hash is found \b NULL will be returned. + */ + HashString const * find(char const * const type) const; + HashString const * find(std::string const &type) const { return find(type.c_str()); } + + /** finds the filesize hash and returns it as number + * + * @return beware: if the size isn't known we return \b 0 here, + * just like we would do for an empty file. If that is a problem + * for you have to get the size manually out of the list. + */ + unsigned long long FileSize() const; + + /** sets the filesize hash + * + * @param Size of the file + * @return @see #push_back + */ + bool FileSize(unsigned long long const Size); + + /** check if the given hash type is supported + * + * @param type to check + * @return true if supported, otherwise false + */ + static APT_PURE bool supported(char const * const type); + /** add the given #HashString to the list + * + * @param hashString to add + * @return true if the hash is added because it is supported and + * not already a different hash of the same type included, otherwise false + */ + bool push_back(const HashString &hashString); + /** @return size of the list of HashStrings */ + size_t size() const { return list.size(); } + + /** verify file against all hashes in the list + * + * @param filename to verify + * @return true if the file matches the hashsum, otherwise false + */ + bool VerifyFile(std::string filename) const; + + /** is the list empty ? + * + * @return \b true if the list is empty, otherwise \b false + */ + bool empty() const { return list.empty(); } + + /** has the list at least one good entry + * + * similar to #empty, but handles forced hashes. + * + * @return if no hash is forced, same result as #empty, + * if one is forced \b true if this has is available, \b false otherwise + */ + bool usable() const; + + typedef std::vector<HashString>::const_iterator const_iterator; + + /** iterator to the first element */ + const_iterator begin() const { return list.begin(); } + + /** iterator to the end element */ + const_iterator end() const { return list.end(); } + + /** start fresh with a clear list */ + void clear() { list.clear(); } + + /** compare two HashStringList for similarity. + * + * Two lists are similar if at least one hashtype is in both lists + * and the hashsum matches. All hashes are checked by default, + * if one doesn't match false is returned regardless of how many + * matched before. If a hash is forced, only this hash is compared, + * all others are ignored. + */ + bool operator==(HashStringList const &other) const; + bool operator!=(HashStringList const &other) const; + + HashStringList() {} + + // simplifying API-compatibility constructors + HashStringList(std::string const &hash) { + if (hash.empty() == false) + list.push_back(HashString(hash)); + } + HashStringList(char const * const hash) { + if (hash != NULL && hash[0] != '\0') + list.push_back(HashString(hash)); + } + + private: + std::vector<HashString> list; +}; + +class PrivateHashes; +class Hashes +{ + PrivateHashes * const d; + + public: + /* those will disappear in the future as it is hard to add new ones this way. + * Use Add* to build the results and get them via GetHashStringList() instead */ + APT_DEPRECATED_MSG("Use general .Add* and .GetHashStringList methods instead of hardcoding specific hashes") MD5Summation MD5; + APT_DEPRECATED_MSG("Use general .Add* and .GetHashStringList methods instead of hardcoding specific hashes") SHA1Summation SHA1; + APT_DEPRECATED_MSG("Use general .Add* and .GetHashStringList methods instead of hardcoding specific hashes") SHA256Summation SHA256; + APT_DEPRECATED_MSG("Use general .Add* and .GetHashStringList methods instead of hardcoding specific hashes") SHA512Summation SHA512; + + static const int UntilEOF = 0; + + bool Add(const unsigned char * const Data, unsigned long long const Size) APT_NONNULL(2); + APT_DEPRECATED_MSG("Construct accordingly instead of choosing hashes while adding") bool Add(const unsigned char * const Data, unsigned long long const Size, unsigned int const Hashes) APT_NONNULL(2); + inline bool Add(const char * const Data) APT_NONNULL(2) + {return Add(reinterpret_cast<unsigned char const *>(Data),strlen(Data));}; + inline bool Add(const unsigned char * const Beg,const unsigned char * const End) APT_NONNULL(2,3) + {return Add(Beg,End-Beg);}; + + enum SupportedHashes { MD5SUM = (1 << 0), SHA1SUM = (1 << 1), SHA256SUM = (1 << 2), + SHA512SUM = (1 << 3) }; + bool AddFD(int const Fd,unsigned long long Size = 0); + APT_DEPRECATED_MSG("Construct accordingly instead of choosing hashes while adding") bool AddFD(int const Fd,unsigned long long Size, unsigned int const Hashes); + bool AddFD(FileFd &Fd,unsigned long long Size = 0); + APT_DEPRECATED_MSG("Construct accordingly instead of choosing hashes while adding") bool AddFD(FileFd &Fd,unsigned long long Size, unsigned int const Hashes); + + HashStringList GetHashStringList(); + +APT_IGNORE_DEPRECATED_PUSH + /** create a Hashes object to calculate all supported hashes + * + * If ALL is too much, you can limit which Hashes are calculated + * with the following other constructors which mention explicitly + * which hashes to generate. */ + Hashes(); + /** @param Hashes bitflag composed of #SupportedHashes */ + Hashes(unsigned int const Hashes); + /** @param Hashes is a list of hashes */ + Hashes(HashStringList const &Hashes); + virtual ~Hashes(); +APT_IGNORE_DEPRECATED_POP + + private: + APT_HIDDEN APT_PURE inline unsigned int boolsToFlag(bool const addMD5, bool const addSHA1, bool const addSHA256, bool const addSHA512) + { + unsigned int hashes = ~0; + if (addMD5 == false) hashes &= ~MD5SUM; + if (addSHA1 == false) hashes &= ~SHA1SUM; + if (addSHA256 == false) hashes &= ~SHA256SUM; + if (addSHA512 == false) hashes &= ~SHA512SUM; + return hashes; + } + + public: +APT_IGNORE_DEPRECATED_PUSH + APT_DEPRECATED_MSG("Construct accordingly instead of choosing hashes while adding") bool AddFD(int const Fd, unsigned long long Size, bool const addMD5, + bool const addSHA1, bool const addSHA256, bool const addSHA512) { + return AddFD(Fd, Size, boolsToFlag(addMD5, addSHA1, addSHA256, addSHA512)); + }; + APT_DEPRECATED_MSG("Construct accordingly instead of choosing hashes while adding") bool AddFD(FileFd &Fd, unsigned long long Size, bool const addMD5, + bool const addSHA1, bool const addSHA256, bool const addSHA512) { + return AddFD(Fd, Size, boolsToFlag(addMD5, addSHA1, addSHA256, addSHA512)); + }; +APT_IGNORE_DEPRECATED_POP +}; + +#endif diff --git a/apt-pkg/contrib/hashsum.cc b/apt-pkg/contrib/hashsum.cc new file mode 100644 index 0000000..e8e86e9 --- /dev/null +++ b/apt-pkg/contrib/hashsum.cc @@ -0,0 +1,52 @@ +// Cryptographic API Base +#include <config.h> + +#include <apt-pkg/fileutl.h> + +#include "hashsum_template.h" +#include <algorithm> +#include <unistd.h> + +// Summation::AddFD - Add content of file into the checksum /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool SummationImplementation::AddFD(int const Fd, unsigned long long Size) { + unsigned char Buf[64 * 64]; + bool const ToEOF = (Size == 0); + while (Size != 0 || ToEOF) + { + unsigned long long n = sizeof(Buf); + if (!ToEOF) n = std::min(Size, n); + ssize_t const Res = read(Fd, Buf, n); + if (Res < 0 || (!ToEOF && Res != (ssize_t) n)) // error, or short read + return false; + if (ToEOF && Res == 0) // EOF + break; + Size -= Res; + Add(Buf,Res); + } + return true; +} +bool SummationImplementation::AddFD(FileFd &Fd, unsigned long long Size) { + unsigned char Buf[64 * 64]; + bool const ToEOF = (Size == 0); + while (Size != 0 || ToEOF) + { + unsigned long long n = sizeof(Buf); + if (!ToEOF) n = std::min(Size, n); + unsigned long long a = 0; + if (Fd.Read(Buf, n, &a) == false) // error + return false; + if (ToEOF == false) + { + if (a != n) // short read + return false; + } + else if (a == 0) // EOF + break; + Size -= a; + Add(Buf, a); + } + return true; +} + /*}}}*/ diff --git a/apt-pkg/contrib/hashsum_template.h b/apt-pkg/contrib/hashsum_template.h new file mode 100644 index 0000000..f11fc2f --- /dev/null +++ b/apt-pkg/contrib/hashsum_template.h @@ -0,0 +1,141 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + HashSumValueTemplate - Generic Storage for a hash value + + ##################################################################### */ + /*}}}*/ +#ifndef APTPKG_HASHSUM_TEMPLATE_H +#define APTPKG_HASHSUM_TEMPLATE_H + +#include <cstring> +#include <string> +#ifdef APT_PKG_EXPOSE_STRING_VIEW +#include <apt-pkg/string_view.h> +#endif + +#include <apt-pkg/strutl.h> + +#ifndef APT_10_CLEANER_HEADERS +#include <apt-pkg/fileutl.h> +#include <algorithm> +#include <stdint.h> +#endif +#ifndef APT_8_CLEANER_HEADERS +using std::string; +using std::min; +#endif + +class FileFd; + +template<int N> +class HashSumValue +{ + unsigned char Sum[N/8]; + + public: + + // Accessors + bool operator ==(const HashSumValue &rhs) const + { + return memcmp(Sum,rhs.Sum,sizeof(Sum)) == 0; + } + bool operator !=(const HashSumValue &rhs) const + { + return memcmp(Sum,rhs.Sum,sizeof(Sum)) != 0; + } + + std::string Value() const + { + char Conv[16] = + { '0','1','2','3','4','5','6','7','8','9','a','b', + 'c','d','e','f' + }; + char Result[((N/8)*2)+1]; + Result[(N/8)*2] = 0; + + // Convert each char into two letters + int J = 0; + int I = 0; + for (; I != (N/8)*2; J++,I += 2) + { + Result[I] = Conv[Sum[J] >> 4]; + Result[I + 1] = Conv[Sum[J] & 0xF]; + } + return std::string(Result); + } + + inline void Value(unsigned char S[N/8]) + { + for (int I = 0; I != sizeof(Sum); ++I) + S[I] = Sum[I]; + } + + inline operator std::string() const + { + return Value(); + } + +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN bool Set(APT::StringView Str) + { + return Hex2Num(Str,Sum,sizeof(Sum)); + } +#else + bool Set(std::string Str) + { + return Hex2Num(Str,Sum,sizeof(Sum)); + } +#endif + inline void Set(unsigned char S[N/8]) + { + for (int I = 0; I != sizeof(Sum); ++I) + Sum[I] = S[I]; + } + + explicit HashSumValue(std::string const &Str) + { + memset(Sum,0,sizeof(Sum)); + Set(Str); + } +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN explicit HashSumValue(APT::StringView const &Str) + { + memset(Sum,0,sizeof(Sum)); + Set(Str); + } + APT_HIDDEN explicit HashSumValue(const char *Str) + { + memset(Sum,0,sizeof(Sum)); + Set(Str); + } +#endif + HashSumValue() + { + memset(Sum,0,sizeof(Sum)); + } +}; + +class SummationImplementation +{ + public: + virtual bool Add(const unsigned char *inbuf, unsigned long long inlen) APT_NONNULL(2) = 0; + inline bool Add(const char *inbuf, unsigned long long const inlen) APT_NONNULL(2) + { return Add(reinterpret_cast<const unsigned char *>(inbuf), inlen); } + + inline bool Add(const unsigned char *Data) APT_NONNULL(2) + { return Add(Data, strlen(reinterpret_cast<const char *>(Data))); } + inline bool Add(const char *Data) APT_NONNULL(2) + { return Add(reinterpret_cast<const unsigned char *>(Data), strlen(Data)); } + + inline bool Add(const unsigned char *Beg, const unsigned char *End) APT_NONNULL(2,3) + { return Add(Beg, End - Beg); } + inline bool Add(const char *Beg, const char *End) APT_NONNULL(2,3) + { return Add(reinterpret_cast<const unsigned char *>(Beg), End - Beg); } + + bool AddFD(int Fd, unsigned long long Size = 0); + bool AddFD(FileFd &Fd, unsigned long long Size = 0); +}; + +#endif diff --git a/apt-pkg/contrib/macros.h b/apt-pkg/contrib/macros.h new file mode 100644 index 0000000..57d3f6c --- /dev/null +++ b/apt-pkg/contrib/macros.h @@ -0,0 +1,173 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Macros Header - Various useful macro definitions + + This source is placed in the Public Domain, do with it what you will + It was originally written by Brian C. White. + + ##################################################################### */ + /*}}}*/ +// Private header +#ifndef MACROS_H +#define MACROS_H + +// MIN_VAL(SINT16) will return -0x8000 and MAX_VAL(SINT16) = 0x7FFF +#define MIN_VAL(t) (((t)(-1) > 0) ? (t)( 0) : (t)(((1L<<(sizeof(t)*8-1)) ))) +#define MAX_VAL(t) (((t)(-1) > 0) ? (t)(-1) : (t)(((1L<<(sizeof(t)*8-1))-1))) + +// Min/Max functions +#if !defined(MIN) +#if defined(__HIGHC__) +#define MIN(x,y) _min(x,y) +#define MAX(x,y) _max(x,y) +#endif + +// GNU C++ has a min/max operator <coolio> +#if defined(__GNUG__) +#define MIN(A,B) ((A) <? (B)) +#define MAX(A,B) ((A) >? (B)) +#endif + +/* Templates tend to mess up existing code that uses min/max because of the + strict matching requirements */ +#if !defined(MIN) +#define MIN(A,B) ((A) < (B)?(A):(B)) +#define MAX(A,B) ((A) > (B)?(A):(B)) +#endif +#endif + +/* Bound functions, bound will return the value b within the limits a-c + bounv will change b so that it is within the limits of a-c. */ +#define _bound(a,b,c) MIN(c,MAX(b,a)) +#define _boundv(a,b,c) b = _bound(a,b,c) +#define ABS(a) (((a) < (0)) ?-(a) : (a)) + +/* Useful count macro, use on an array of things and it will return the + number of items in the array */ +#define _count(a) (sizeof(a)/sizeof(a[0])) + +// Flag Macros +#define FLAG(f) (1L << (f)) +#define SETFLAG(v,f) ((v) |= FLAG(f)) +#define CLRFLAG(v,f) ((v) &=~FLAG(f)) +#define CHKFLAG(v,f) ((v) & FLAG(f) ? true : false) + +#ifdef __GNUC__ +#define APT_GCC_VERSION (__GNUC__ << 8 | __GNUC_MINOR__) +#else +#define APT_GCC_VERSION 0 +#endif + +/* likely() and unlikely() can be used to mark boolean expressions + as (not) likely true which will help the compiler to optimise */ +#if APT_GCC_VERSION >= 0x0300 + #define likely(x) __builtin_expect (!!(x), 1) + #define unlikely(x) __builtin_expect (!!(x), 0) +#else + #define likely(x) (x) + #define unlikely(x) (x) +#endif + +#if APT_GCC_VERSION >= 0x0300 + #define APT_DEPRECATED __attribute__ ((deprecated)) + #define APT_DEPRECATED_MSG(X) __attribute__ ((deprecated(X))) + // __attribute__((const)) is too dangerous for us, we end up using it wrongly + #define APT_CONST __attribute__((pure)) + #define APT_PURE __attribute__((pure)) + #define APT_NORETURN __attribute__((noreturn)) + #define APT_PRINTF(n) __attribute__((format(printf, n, n + 1))) + #define APT_WEAK __attribute__((weak)); +#else + #define APT_DEPRECATED + #define APT_DEPRECATED_MSG + #define APT_CONST + #define APT_PURE + #define APT_NORETURN + #define APT_PRINTF(n) + #define APT_WEAK +#endif + +#if APT_GCC_VERSION > 0x0302 + #define APT_NONNULL(...) __attribute__((nonnull(__VA_ARGS__))) + #define APT_MUSTCHECK __attribute__((warn_unused_result)) +#else + #define APT_NONNULL(...) + #define APT_MUSTCHECK +#endif + +#if APT_GCC_VERSION >= 0x0400 + #define APT_SENTINEL __attribute__((sentinel)) + #define APT_PUBLIC __attribute__ ((visibility ("default"))) + #define APT_HIDDEN __attribute__ ((visibility ("hidden"))) +#else + #define APT_SENTINEL + #define APT_PUBLIC + #define APT_HIDDEN +#endif + +// cold functions are unlikely() to be called +#if APT_GCC_VERSION >= 0x0403 + #define APT_COLD __attribute__ ((__cold__)) + #define APT_HOT __attribute__ ((__hot__)) +#else + #define APT_COLD + #define APT_HOT +#endif + +#ifndef APT_10_CLEANER_HEADERS +#if APT_GCC_VERSION >= 0x0300 + #define __must_check __attribute__ ((warn_unused_result)) + #define __deprecated __attribute__ ((deprecated)) + #define __attrib_const __attribute__ ((__const__)) + #define __like_printf(n) __attribute__((format(printf, n, n + 1))) +#else + #define __must_check /* no warn_unused_result */ + #define __deprecated /* no deprecated */ + #define __attrib_const /* no const attribute */ + #define __like_printf(n) /* no like-printf */ +#endif +#if APT_GCC_VERSION >= 0x0403 + #define __cold __attribute__ ((__cold__)) + #define __hot __attribute__ ((__hot__)) +#else + #define __cold /* no cold marker */ + #define __hot /* no hot marker */ +#endif +#endif + +#if __GNUC__ >= 4 + #define APT_IGNORE_DEPRECATED_PUSH \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + #define APT_IGNORE_DEPRECATED_POP \ + _Pragma("GCC diagnostic pop") + /* gcc has various problems with this shortcut, so prefer the long form */ + #define APT_IGNORE_DEPRECATED(XXX) \ + APT_IGNORE_DEPRECATED_PUSH \ + XXX \ + APT_IGNORE_DEPRECATED_POP +#else + #define APT_IGNORE_DEPRECATED_PUSH + #define APT_IGNORE_DEPRECATED_POP + #define APT_IGNORE_DEPRECATED(XXX) XXX +#endif + +#if __cplusplus >= 201103L + #define APT_OVERRIDE override +#else + #define APT_OVERRIDE /* no c++11 standard */ +#endif + +// These lines are extracted by the makefiles and the buildsystem +// Increasing MAJOR or MINOR results in the need of recompiling all +// reverse-dependencies of libapt-pkg against the new SONAME. +// Non-ABI-Breaks should only increase RELEASE number. +// See also buildlib/libversion.mak +#define APT_PKG_MAJOR 5 +#define APT_PKG_MINOR 0 +#define APT_PKG_RELEASE 2 +#define APT_PKG_ABI ((APT_PKG_MAJOR * 100) + APT_PKG_MINOR) + +#endif diff --git a/apt-pkg/contrib/md5.cc b/apt-pkg/contrib/md5.cc new file mode 100644 index 0000000..c3b5299 --- /dev/null +++ b/apt-pkg/contrib/md5.cc @@ -0,0 +1,279 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + MD5Sum - MD5 Message Digest Algorithm. + + This code implements the MD5 message-digest algorithm. The algorithm is + due to Ron Rivest. This code was written by Colin Plumb in 1993, no + copyright is claimed. This code is in the public domain; do with it what + you wish. + + Equivalent code is available from RSA Data Security, Inc. This code has + been tested against that, and is equivalent, except that you don't need to + include two pages of legalese with every copy. + + To compute the message digest of a chunk of bytes, instantiate the class, + and repeatedly call one of the Add() members. When finished the Result + method will return the Hash and finalize the value. + + Changed so as no longer to depend on Colin Plumb's `usual.h' header + definitions; now uses stuff from dpkg's config.h. + - Ian Jackson <ijackson@nyx.cs.du.edu>. + + Changed into a C++ interface and made work with APT's config.h. + - Jason Gunthorpe <jgg@gpu.srv.ualberta.ca> + + Still in the public domain. + + The classes use arrays of char that are a specific size. We cast those + arrays to uint8_t's and go from there. This allows us to advoid using + the uncommon inttypes.h in a public header or internally newing memory. + In theory if C9x becomes nicely accepted + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/md5.h> + +#include <stdint.h> +#include <string.h> + /*}}}*/ + +// byteSwap - Swap bytes in a buffer /*{{{*/ +// --------------------------------------------------------------------- +/* Swap n 32 bit longs in given buffer */ +#ifdef WORDS_BIGENDIAN +static void byteSwap(uint32_t *buf, unsigned words) +{ + uint8_t *p = (uint8_t *)buf; + + do + { + *buf++ = (uint32_t)((unsigned)p[3] << 8 | p[2]) << 16 | + ((unsigned)p[1] << 8 | p[0]); + p += 4; + } while (--words); +} +#else +#define byteSwap(buf,words) +#endif + /*}}}*/ +// MD5Transform - Alters an existing MD5 hash /*{{{*/ +// --------------------------------------------------------------------- +/* The core of the MD5 algorithm, this alters an existing MD5 hash to + reflect the addition of 16 longwords of new data. Add blocks + the data and converts bytes into longwords for this routine. */ + +// The four core functions - F1 is optimized somewhat +// #define F1(x, y, z) (x & y | ~x & z) +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +// This is the central step in the MD5 algorithm. +#define MD5STEP(f,w,x,y,z,in,s) \ + (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x) + +static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + /*}}}*/ +// MD5Summation::MD5Summation - Initialize the summer /*{{{*/ +// --------------------------------------------------------------------- +/* This assigns the deep magic initial values */ +MD5Summation::MD5Summation() +{ + uint32_t *buf = (uint32_t *)Buf; + uint32_t *bytes = (uint32_t *)Bytes; + + buf[0] = 0x67452301; + buf[1] = 0xefcdab89; + buf[2] = 0x98badcfe; + buf[3] = 0x10325476; + + bytes[0] = 0; + bytes[1] = 0; + Done = false; +} + /*}}}*/ +// MD5Summation::Add - 'Add' a data set to the hash /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool MD5Summation::Add(const unsigned char *data,unsigned long long len) +{ + if (Done == true) + return false; + if (len == 0) + return true; + + uint32_t *buf = (uint32_t *)Buf; + uint32_t *bytes = (uint32_t *)Bytes; + uint32_t *in = (uint32_t *)In; + + // Update byte count and carry (this could be done with a long long?) + uint32_t t = bytes[0]; + if ((bytes[0] = t + len) < t) + bytes[1]++; + + // Space available (at least 1) + t = 64 - (t & 0x3f); + if (t > len) + { + memcpy((unsigned char *)in + 64 - t,data,len); + return true; + } + + // First chunk is an odd size + memcpy((unsigned char *)in + 64 - t,data,t); + byteSwap(in, 16); + MD5Transform(buf,in); + data += t; + len -= t; + + // Process data in 64-byte chunks + while (len >= 64) + { + memcpy(in,data,64); + byteSwap(in,16); + MD5Transform(buf,in); + data += 64; + len -= 64; + } + + // Handle any remaining bytes of data. + memcpy(in,data,len); + + return true; +} + /*}}}*/ +// MD5Summation::Result - Returns the value of the sum /*{{{*/ +// --------------------------------------------------------------------- +/* Because this must add in the last bytes of the series it prevents anyone + from calling add after. */ +MD5SumValue MD5Summation::Result() +{ + uint32_t *buf = (uint32_t *)Buf; + uint32_t *bytes = (uint32_t *)Bytes; + uint32_t *in = (uint32_t *)In; + + if (Done == false) + { + // Number of bytes in In + int count = bytes[0] & 0x3f; + unsigned char *p = (unsigned char *)in + count; + + // Set the first char of padding to 0x80. There is always room. + *p++ = 0x80; + + // Bytes of padding needed to make 56 bytes (-8..55) + count = 56 - 1 - count; + + // Padding forces an extra block + if (count < 0) + { + memset(p,0,count + 8); + byteSwap(in, 16); + MD5Transform(buf,in); + p = (unsigned char *)in; + count = 56; + } + + memset(p, 0, count); + byteSwap(in, 14); + + // Append length in bits and transform + in[14] = bytes[0] << 3; + in[15] = bytes[1] << 3 | bytes[0] >> 29; + MD5Transform(buf,in); + byteSwap(buf,4); + Done = true; + } + + MD5SumValue V; + V.Set((unsigned char *)buf); + return V; +} + /*}}}*/ diff --git a/apt-pkg/contrib/md5.h b/apt-pkg/contrib/md5.h new file mode 100644 index 0000000..38b6c68 --- /dev/null +++ b/apt-pkg/contrib/md5.h @@ -0,0 +1,58 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + MD5SumValue - Storage for a MD5Sum + MD5Summation - MD5 Message Digest Algorithm. + + This is a C++ interface to a set of MD5Sum functions. The class can + store a MD5Sum in 16 bytes of memory. + + A MD5Sum is used to generate a (hopefully) unique 16 byte number for a + block of data. This can be used to guard against corruption of a file. + MD5 should not be used for tamper protection, use SHA or something more + secure. + + There are two classes because computing a MD5 is not a continual + operation unless 64 byte blocks are used. Also the summation requires an + extra 18*4 bytes to operate. + + ##################################################################### */ + /*}}}*/ +#ifndef APTPKG_MD5_H +#define APTPKG_MD5_H + +#include <stdint.h> + +#include "hashsum_template.h" + +#ifndef APT_10_CLEANER_HEADERS +#include <algorithm> +#include <cstring> +#include <string> +#endif +#ifndef APT_8_CLEANER_HEADERS +using std::string; +using std::min; +#endif + +typedef HashSumValue<128> MD5SumValue; + +class MD5Summation : public SummationImplementation +{ + uint32_t Buf[4]; + unsigned char Bytes[2*4]; + unsigned char In[16*4]; + bool Done; + + public: + + bool Add(const unsigned char *inbuf, unsigned long long inlen) APT_OVERRIDE APT_NONNULL(2); + using SummationImplementation::Add; + + MD5SumValue Result(); + + MD5Summation(); +}; + +#endif diff --git a/apt-pkg/contrib/mmap.cc b/apt-pkg/contrib/mmap.cc new file mode 100644 index 0000000..ee6a21c --- /dev/null +++ b/apt-pkg/contrib/mmap.cc @@ -0,0 +1,502 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + MMap Class - Provides 'real' mmap or a faked mmap using read(). + + MMap cover class. + + Some broken versions of glibc2 (libc6) have a broken definition + of mmap that accepts a char * -- all other systems (and libc5) use + void *. We can't safely do anything here that would be portable, so + libc6 generates warnings -- which should be errors, g++ isn't properly + strict. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#define _DEFAULT_SOURCE +#include <config.h> + +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/mmap.h> + +#include <cstring> +#include <string> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ + +// MMap::MMap - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +MMap::MMap(FileFd &F,unsigned long Flags) : Flags(Flags), iSize(0), + Base(nullptr), SyncToFd(nullptr) +{ + if ((Flags & NoImmMap) != NoImmMap) + Map(F); +} + /*}}}*/ +// MMap::MMap - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +MMap::MMap(unsigned long Flags) : Flags(Flags), iSize(0), + Base(nullptr), SyncToFd(nullptr) +{ +} + /*}}}*/ +// MMap::~MMap - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +MMap::~MMap() +{ + Close(); +} + /*}}}*/ +// MMap::Map - Perform the mapping /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool MMap::Map(FileFd &Fd) +{ + iSize = Fd.Size(); + + // Set the permissions. + int Prot = PROT_READ; + int Map = MAP_SHARED; + if ((Flags & ReadOnly) != ReadOnly) + Prot |= PROT_WRITE; + if ((Flags & Public) != Public) + Map = MAP_PRIVATE; + + if (iSize == 0) + return _error->Error(_("Can't mmap an empty file")); + + // We can't mmap compressed fd's directly, so we need to read it completely + if (Fd.IsCompressed() == true) + { + if ((Flags & ReadOnly) != ReadOnly) + return _error->Error("Compressed file %s can only be mapped readonly", Fd.Name().c_str()); + Base = malloc(iSize); + if (unlikely(Base == nullptr)) + return _error->Errno("MMap-compressed-malloc", _("Couldn't make mmap of %llu bytes"), iSize); + SyncToFd = new FileFd(); + if (Fd.Seek(0L) == false || Fd.Read(Base, iSize) == false) + return _error->Error("Compressed file %s can't be read into mmap", Fd.Name().c_str()); + return true; + } + + // Map it. + Base = (Flags & Fallback) ? MAP_FAILED : mmap(0,iSize,Prot,Map,Fd.Fd(),0); + if (Base == MAP_FAILED) + { + if (errno == ENODEV || errno == EINVAL || (Flags & Fallback)) + { + // The filesystem doesn't support this particular kind of mmap. + // So we allocate a buffer and read the whole file into it. + if ((Flags & ReadOnly) == ReadOnly) + { + // for readonly, we don't need sync, so make it simple + Base = malloc(iSize); + if (unlikely(Base == nullptr)) + return _error->Errno("MMap-malloc", _("Couldn't make mmap of %llu bytes"), iSize); + SyncToFd = new FileFd(); + return Fd.Read(Base, iSize); + } + // FIXME: Writing to compressed fd's ? + int const dupped_fd = dup(Fd.Fd()); + if (dupped_fd == -1) + return _error->Errno("mmap", _("Couldn't duplicate file descriptor %i"), Fd.Fd()); + + Base = calloc(iSize, 1); + if (unlikely(Base == nullptr)) + return _error->Errno("MMap-calloc", _("Couldn't make mmap of %llu bytes"), iSize); + SyncToFd = new FileFd (dupped_fd); + if (!SyncToFd->Seek(0L) || !SyncToFd->Read(Base, iSize)) + return false; + } + else + return _error->Errno("MMap-mmap", _("Couldn't make mmap of %llu bytes"), iSize); + } + + return true; +} + /*}}}*/ +// MMap::Close - Close the map /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool MMap::Close(bool DoSync) +{ + if ((Flags & UnMapped) == UnMapped || validData() == false || iSize == 0) + return true; + + if (DoSync == true) + Sync(); + + if (SyncToFd != NULL) + { + free(Base); + delete SyncToFd; + SyncToFd = NULL; + } + else + { + if (munmap((char *)Base, iSize) != 0) + _error->WarningE("mmap", _("Unable to close mmap")); + } + + iSize = 0; + Base = 0; + return true; +} + /*}}}*/ +// MMap::Sync - Synchronize the map with the disk /*{{{*/ +// --------------------------------------------------------------------- +/* This is done in synchronous mode - the docs indicate that this will + not return till all IO is complete */ +bool MMap::Sync() +{ + if ((Flags & UnMapped) == UnMapped) + return true; + + if ((Flags & ReadOnly) != ReadOnly) + { + if (SyncToFd != NULL) + { + if (!SyncToFd->Seek(0) || !SyncToFd->Write(Base, iSize)) + return false; + } + else + { +#ifdef _POSIX_SYNCHRONIZED_IO + if (msync((char *)Base, iSize, MS_SYNC) < 0) + return _error->Errno("msync", _("Unable to synchronize mmap")); +#endif + } + } + return true; +} + /*}}}*/ +// MMap::Sync - Synchronize a section of the file to disk /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool MMap::Sync(unsigned long Start,unsigned long Stop) +{ + if ((Flags & UnMapped) == UnMapped) + return true; + + if ((Flags & ReadOnly) != ReadOnly) + { + if (SyncToFd != 0) + { + if (!SyncToFd->Seek(0) || + !SyncToFd->Write (((char *)Base)+Start, Stop-Start)) + return false; + } + else + { +#ifdef _POSIX_SYNCHRONIZED_IO + unsigned long long const PSize = sysconf(_SC_PAGESIZE); + if (msync((char *)Base+(Start/PSize)*PSize, Stop - Start, MS_SYNC) < 0) + return _error->Errno("msync", _("Unable to synchronize mmap")); +#endif + } + } + return true; +} + /*}}}*/ + +// DynamicMMap::DynamicMMap - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +DynamicMMap::DynamicMMap(FileFd &F,unsigned long Flags,unsigned long const &Workspace, + unsigned long const &Grow, unsigned long const &Limit) : + MMap(F,Flags | NoImmMap), Fd(&F), WorkSpace(Workspace), + GrowFactor(Grow), Limit(Limit) +{ + // disable Moveable if we don't grow + if (Grow == 0) + this->Flags &= ~Moveable; + +#ifndef __linux__ + // kfreebsd doesn't have mremap, so we use the fallback + if ((this->Flags & Moveable) == Moveable) + this->Flags |= Fallback; +#endif + + unsigned long long EndOfFile = Fd->Size(); + if (EndOfFile > WorkSpace) + WorkSpace = EndOfFile; + else if(WorkSpace > 0) + { + Fd->Seek(WorkSpace - 1); + char C = 0; + Fd->Write(&C,sizeof(C)); + } + + Map(F); + iSize = EndOfFile; +} + /*}}}*/ +// DynamicMMap::DynamicMMap - Constructor for a non-file backed map /*{{{*/ +// --------------------------------------------------------------------- +/* We try here to use mmap to reserve some space - this is much more + cooler than the fallback solution to simply allocate a char array + and could come in handy later than we are able to grow such an mmap */ +DynamicMMap::DynamicMMap(unsigned long Flags,unsigned long const &WorkSpace, + unsigned long const &Grow, unsigned long const &Limit) : + MMap(Flags | NoImmMap | UnMapped), Fd(0), WorkSpace(WorkSpace), + GrowFactor(Grow), Limit(Limit) +{ + // disable Moveable if we don't grow + if (Grow == 0) + this->Flags &= ~Moveable; + +#ifndef __linux__ + // kfreebsd doesn't have mremap, so we use the fallback + if ((this->Flags & Moveable) == Moveable) + this->Flags |= Fallback; +#endif + +#ifdef _POSIX_MAPPED_FILES + if ((this->Flags & Fallback) != Fallback) { + // Set the permissions. + int Prot = PROT_READ; +#ifdef MAP_ANONYMOUS + int Map = MAP_PRIVATE | MAP_ANONYMOUS; +#else + int Map = MAP_PRIVATE | MAP_ANON; +#endif + if ((this->Flags & ReadOnly) != ReadOnly) + Prot |= PROT_WRITE; + if ((this->Flags & Public) == Public) +#ifdef MAP_ANONYMOUS + Map = MAP_SHARED | MAP_ANONYMOUS; +#else + Map = MAP_SHARED | MAP_ANON; +#endif + + // use anonymous mmap() to get the memory + Base = (unsigned char*) mmap(0, WorkSpace, Prot, Map, -1, 0); + + if(Base == MAP_FAILED) + _error->Errno("DynamicMMap",_("Couldn't make mmap of %lu bytes"),WorkSpace); + + iSize = 0; + return; + } +#endif + // fallback to a static allocated space + Base = calloc(WorkSpace, 1); + iSize = 0; +} + /*}}}*/ +// DynamicMMap::~DynamicMMap - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* We truncate the file to the size of the memory data set */ +DynamicMMap::~DynamicMMap() +{ + if (Fd == 0) + { + if (validData() == false) + return; +#ifdef _POSIX_MAPPED_FILES + munmap(Base, WorkSpace); +#else + free(Base); +#endif + return; + } + + unsigned long long EndOfFile = iSize; + iSize = WorkSpace; + Close(false); + if(ftruncate(Fd->Fd(),EndOfFile) < 0) + _error->Errno("ftruncate", _("Failed to truncate file")); +} + /*}}}*/ +// DynamicMMap::RawAllocate - Allocate a raw chunk of unaligned space /*{{{*/ +// --------------------------------------------------------------------- +/* This allocates a block of memory aligned to the given size */ +unsigned long DynamicMMap::RawAllocate(unsigned long long Size,unsigned long Aln) +{ + unsigned long long Result = iSize; + if (Aln != 0) + Result += Aln - (iSize%Aln); + + iSize = Result + Size; + + // try to grow the buffer + while(Result + Size > WorkSpace) + { + if(!Grow()) + { + _error->Fatal(_("Dynamic MMap ran out of room. Please increase the size " + "of APT::Cache-Start. Current value: %lu. (man 5 apt.conf)"), WorkSpace); + return 0; + } + } + return Result; +} + /*}}}*/ +// DynamicMMap::Allocate - Pooled aligned allocation /*{{{*/ +// --------------------------------------------------------------------- +/* This allocates an Item of size ItemSize so that it is aligned to its + size in the file. */ +unsigned long DynamicMMap::Allocate(unsigned long ItemSize) +{ + if (unlikely(ItemSize == 0)) + { + _error->Fatal("Can't allocate an item of size zero"); + return 0; + } + + // Look for a matching pool entry + Pool *I; + Pool *Empty = 0; + for (I = Pools; I != Pools + PoolCount; ++I) + { + if (I->ItemSize == 0) + Empty = I; + if (I->ItemSize == ItemSize) + break; + } + // No pool is allocated, use an unallocated one + if (I == Pools + PoolCount) + { + // Woops, we ran out, the calling code should allocate more. + if (Empty == 0) + { + _error->Error("Ran out of allocation pools"); + return 0; + } + + I = Empty; + I->ItemSize = ItemSize; + I->Count = 0; + } + + unsigned long Result = 0; + // Out of space, allocate some more + if (I->Count == 0) + { + const unsigned long size = 20*1024; + I->Count = size/ItemSize; + Pool* oldPools = Pools; + _error->PushToStack(); + Result = RawAllocate(size,ItemSize); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + if (Pools != oldPools) + I += Pools - oldPools; + + // Does the allocation failed ? + if (Result == 0 && newError) + return 0; + I->Start = Result; + } + else + Result = I->Start; + + I->Count--; + I->Start += ItemSize; + return Result/ItemSize; +} + /*}}}*/ +// DynamicMMap::WriteString - Write a string to the file /*{{{*/ +// --------------------------------------------------------------------- +/* Strings are aligned to 16 bytes */ +unsigned long DynamicMMap::WriteString(const char *String, + unsigned long Len) +{ + if (Len == std::numeric_limits<unsigned long>::max()) + Len = strlen(String); + + _error->PushToStack(); + unsigned long Result = RawAllocate(Len+1+sizeof(uint16_t),sizeof(uint16_t)); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + + if (Base == NULL || (Result == 0 && newError)) + return 0; + + if (Len >= std::numeric_limits<uint16_t>::max()) + abort(); + + uint16_t LenToWrite = Len; + memcpy((char *)Base + Result, &LenToWrite, sizeof(LenToWrite)); + Result += + sizeof(LenToWrite); + + memcpy((char *)Base + Result,String,Len); + ((char *)Base)[Result + Len] = 0; + return Result; +} + /*}}}*/ +// DynamicMMap::Grow - Grow the mmap /*{{{*/ +// --------------------------------------------------------------------- +/* This method is a wrapper around different methods to (try to) grow + a mmap (or our char[]-fallback). Encounterable environments: + 1. Moveable + !Fallback + linux -> mremap with MREMAP_MAYMOVE + 2. Moveable + !Fallback + !linux -> not possible (forbidden by constructor) + 3. Moveable + Fallback -> realloc + 4. !Moveable + !Fallback + linux -> mremap alone - which will fail in 99,9% + 5. !Moveable + !Fallback + !linux -> not possible (forbidden by constructor) + 6. !Moveable + Fallback -> not possible + [ While Moveable and Fallback stands for the equally named flags and + "linux" indicates a linux kernel instead of a freebsd kernel. ] + So what you can see here is, that a MMAP which want to be growable need + to be moveable to have a real chance but that this method will at least try + the nearly impossible 4 to grow it before it finally give up: Never say never. */ +bool DynamicMMap::Grow() { + if (Limit != 0 && WorkSpace >= Limit) + return _error->Error(_("Unable to increase the size of the MMap as the " + "limit of %lu bytes is already reached."), Limit); + if (GrowFactor <= 0) + return _error->Error(_("Unable to increase size of the MMap as automatic growing is disabled by user.")); + + unsigned long long const newSize = WorkSpace + GrowFactor; + + if(Fd != 0) { + Fd->Seek(newSize - 1); + char C = 0; + Fd->Write(&C,sizeof(C)); + } + + unsigned long const poolOffset = Pools - ((Pool*) Base); + + if ((Flags & Fallback) != Fallback) { +#if defined(_POSIX_MAPPED_FILES) && defined(__linux__) + #ifdef MREMAP_MAYMOVE + + if ((Flags & Moveable) == Moveable) + Base = mremap(Base, WorkSpace, newSize, MREMAP_MAYMOVE); + else + #endif + Base = mremap(Base, WorkSpace, newSize, 0); + + if(Base == MAP_FAILED) + return false; +#else + return false; +#endif + } else { + if ((Flags & Moveable) != Moveable) + return false; + + Base = realloc(Base, newSize); + if (Base == NULL) + return false; + else + /* Set new memory to 0 */ + memset((char*)Base + WorkSpace, 0, newSize - WorkSpace); + } + + Pools =(Pool*) Base + poolOffset; + WorkSpace = newSize; + return true; +} + /*}}}*/ diff --git a/apt-pkg/contrib/mmap.h b/apt-pkg/contrib/mmap.h new file mode 100644 index 0000000..63d54d2 --- /dev/null +++ b/apt-pkg/contrib/mmap.h @@ -0,0 +1,120 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + MMap Class - Provides 'real' mmap or a faked mmap using read(). + + The purpose of this code is to provide a generic way for clients to + access the mmap function. In environments that do not support mmap + from file fd's this function will use read and normal allocated + memory. + + Writing to a public mmap will always fully commit all changes when the + class is deleted. Ie it will rewrite the file, unless it is readonly + + The DynamicMMap class is used to help the on-disk data structure + generators. It provides a large allocated workspace and members + to allocate space from the workspace in an efficient fashion. + + This source is placed in the Public Domain, do with it what you will + It was originally written by Jason Gunthorpe. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_MMAP_H +#define PKGLIB_MMAP_H + +#include <string> +#include <limits> + +#include <sys/mman.h> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/fileutl.h> +using std::string; +#endif + +class FileFd; + +/* This should be a 32 bit type, larger types use too much ram and smaller + types are too small. Where ever possible 'unsigned long' should be used + instead of this internal type */ +typedef unsigned int map_ptrloc; + +class MMap +{ + protected: + + unsigned long Flags; + unsigned long long iSize; + void *Base; + + // In case mmap can not be used, we keep a dup of the file + // descriptor that should have been mmaped so that we can write to + // the file in Sync(). + FileFd *SyncToFd; + + bool Map(FileFd &Fd); + bool Close(bool DoSync = true); + + public: + + enum OpenFlags {NoImmMap = (1<<0),Public = (1<<1),ReadOnly = (1<<2), + UnMapped = (1<<3), Moveable = (1<<4), Fallback = (1 << 5)}; + + // Simple accessors + inline operator void *() {return Base;}; + inline void *Data() {return Base;}; + inline unsigned long long Size() {return iSize;}; + inline void AddSize(unsigned long long const size) {iSize += size;}; + inline bool validData() const { return Base != MAP_FAILED && Base != 0; }; + + // File manipulators + bool Sync(); + bool Sync(unsigned long Start,unsigned long Stop); + + MMap(FileFd &F,unsigned long Flags); + MMap(unsigned long Flags); + virtual ~MMap(); +}; + +class DynamicMMap : public MMap +{ + public: + + // This is the allocation pool structure + struct Pool + { + unsigned long ItemSize; + unsigned long Start; + unsigned long Count; + }; + + protected: + + FileFd *Fd; + unsigned long WorkSpace; + unsigned long const GrowFactor; + unsigned long const Limit; + Pool *Pools; + unsigned int PoolCount; + + bool Grow(); + + public: + + // Allocation + unsigned long RawAllocate(unsigned long long Size,unsigned long Aln = 0); + unsigned long Allocate(unsigned long ItemSize); + unsigned long WriteString(const char *String,unsigned long Len = std::numeric_limits<unsigned long>::max()); + inline unsigned long WriteString(const std::string &S) {return WriteString(S.c_str(),S.length());}; + void UsePools(Pool &P,unsigned int Count) {Pools = &P; PoolCount = Count;}; + + DynamicMMap(FileFd &F,unsigned long Flags,unsigned long const &WorkSpace = 2*1024*1024, + unsigned long const &Grow = 1024*1024, unsigned long const &Limit = 0); + DynamicMMap(unsigned long Flags,unsigned long const &WorkSpace = 2*1024*1024, + unsigned long const &Grow = 1024*1024, unsigned long const &Limit = 0); + virtual ~DynamicMMap(); +}; + +#endif diff --git a/apt-pkg/contrib/netrc.cc b/apt-pkg/contrib/netrc.cc new file mode 100644 index 0000000..48114ba --- /dev/null +++ b/apt-pkg/contrib/netrc.cc @@ -0,0 +1,195 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + netrc file parser - returns the login and password of a give host in + a specified netrc-type file + + Originally written by Daniel Stenberg, <daniel@haxx.se>, et al. and + placed into the Public Domain, do with it what you will. + + ##################################################################### */ + /*}}}*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/strutl.h> + +#include <iostream> + +#include "netrc.h" + +/* Get user and password from .netrc when given a machine name */ +bool MaybeAddAuth(FileFd &NetRCFile, URI &Uri) +{ + if (Uri.User.empty() == false || Uri.Password.empty() == false) + return true; + if (NetRCFile.IsOpen() == false || NetRCFile.Failed()) + return false; + auto const Debug = _config->FindB("Debug::Acquire::netrc", false); + + std::string lookfor; + if (Uri.Port != 0) + strprintf(lookfor, "%s:%i%s", Uri.Host.c_str(), Uri.Port, Uri.Path.c_str()); + else + lookfor.append(Uri.Host).append(Uri.Path); + + enum + { + NO, + MACHINE, + GOOD_MACHINE, + LOGIN, + PASSWORD + } active_token = NO; + std::string line; + while (NetRCFile.Eof() == false || line.empty() == false) + { + if (line.empty()) + { + if (NetRCFile.ReadLine(line) == false) + break; + else if (line.empty()) + continue; + } + auto tokenend = line.find_first_of("\t "); + std::string token; + if (tokenend != std::string::npos) + { + token = line.substr(0, tokenend); + line.erase(0, tokenend + 1); + } + else + std::swap(line, token); + if (token.empty()) + continue; + switch (active_token) + { + case NO: + if (token == "machine") + active_token = MACHINE; + break; + case MACHINE: + if (token.find('/') == std::string::npos) + { + if (Uri.Port != 0 && Uri.Host == token) + active_token = GOOD_MACHINE; + else if (lookfor.compare(0, lookfor.length() - Uri.Path.length(), token) == 0) + active_token = GOOD_MACHINE; + else + active_token = NO; + } + else + { + if (APT::String::Startswith(lookfor, token)) + active_token = GOOD_MACHINE; + else + active_token = NO; + } + break; + case GOOD_MACHINE: + if (token == "login") + active_token = LOGIN; + else if (token == "password") + active_token = PASSWORD; + else if (token == "machine") + { + if (Debug) + std::clog << "MaybeAddAuth: Found matching host adding '" << Uri.User << "' and '" << Uri.Password << "' for " + << (std::string)Uri << " from " << NetRCFile.Name() << std::endl; + return true; + } + break; + case LOGIN: + std::swap(Uri.User, token); + active_token = GOOD_MACHINE; + break; + case PASSWORD: + std::swap(Uri.Password, token); + active_token = GOOD_MACHINE; + break; + } + } + if (active_token == GOOD_MACHINE) + { + if (Debug) + std::clog << "MaybeAddAuth: Found matching host adding '" << Uri.User << "' and '" << Uri.Password << "' for " + << (std::string)Uri << " from " << NetRCFile.Name() << std::endl; + return true; + } + else if (active_token == NO) + { + if (Debug) + std::clog << "MaybeAddAuth: Found no matching host for " + << (std::string)Uri << " from " << NetRCFile.Name() << std::endl; + return true; + } + else if (Debug) + { + std::clog << "MaybeAddAuth: Found no matching host (syntax error: token:"; + switch (active_token) + { + case NO: std::clog << "NO"; break; + case MACHINE: std::clog << "MACHINE"; break; + case GOOD_MACHINE: std::clog << "GOOD_MACHINE"; break; + case LOGIN: std::clog << "LOGIN"; break; + case PASSWORD: std::clog << "PASSWORD"; break; + } + std::clog << ") for " << (std::string)Uri << " from " << NetRCFile.Name() << std::endl; + } + return false; +} + +void maybe_add_auth(URI &Uri, std::string NetRCFile) +{ + if (FileExists(NetRCFile) == false) + return; + FileFd fd; + if (fd.Open(NetRCFile, FileFd::ReadOnly)) + MaybeAddAuth(fd, Uri); +} + +/* Check if we are authorized. */ +bool IsAuthorized(pkgCache::PkgFileIterator const I, std::vector<std::unique_ptr<FileFd>> &authconfs) +{ + if (authconfs.empty()) + { + _error->PushToStack(); + auto const netrc = _config->FindFile("Dir::Etc::netrc"); + if (not netrc.empty()) + { + authconfs.emplace_back(new FileFd()); + authconfs.back()->Open(netrc, FileFd::ReadOnly); + } + + auto const netrcparts = _config->FindDir("Dir::Etc::netrcparts"); + if (not netrcparts.empty()) + { + for (auto const &netrc : GetListOfFilesInDir(netrcparts, "conf", true, true)) + { + authconfs.emplace_back(new FileFd()); + authconfs.back()->Open(netrc, FileFd::ReadOnly); + } + } + _error->RevertToStack(); + } + + // FIXME: Use the full base url + URI uri(std::string("http://") + I.Site() + "/"); + for (auto &authconf : authconfs) + { + if (not authconf->IsOpen()) + continue; + if (not authconf->Seek(0)) + continue; + + MaybeAddAuth(*authconf, uri); + + if (not uri.User.empty() || not uri.Password.empty()) + return true; + } + + return false; +} diff --git a/apt-pkg/contrib/netrc.h b/apt-pkg/contrib/netrc.h new file mode 100644 index 0000000..80d95ac --- /dev/null +++ b/apt-pkg/contrib/netrc.h @@ -0,0 +1,39 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + netrc file parser - returns the login and password of a give host in + a specified netrc-type file + + Originally written by Daniel Stenberg, <daniel@haxx.se>, et al. and + placed into the Public Domain, do with it what you will. + + ##################################################################### */ + /*}}}*/ +#ifndef NETRC_H +#define NETRC_H + +#include <memory> +#include <string> +#include <vector> + +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/strutl.h> +#endif + +#ifndef APT_15_CLEANER_HEADERS +#define DOT_CHAR "." +#define DIR_CHAR "/" +#endif + +class URI; +class FileFd; + +APT_DEPRECATED_MSG("Use FileFd-based MaybeAddAuth instead") +void maybe_add_auth(URI &Uri, std::string NetRCFile); +bool MaybeAddAuth(FileFd &NetRCFile, URI &Uri); +bool IsAuthorized(pkgCache::PkgFileIterator const I, std::vector<std::unique_ptr<FileFd>> &authconfs) APT_HIDDEN; +#endif diff --git a/apt-pkg/contrib/progress.cc b/apt-pkg/contrib/progress.cc new file mode 100644 index 0000000..806bd47 --- /dev/null +++ b/apt-pkg/contrib/progress.cc @@ -0,0 +1,221 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + OpProgress - Operation Progress + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/progress.h> + +#include <cmath> +#include <chrono> +#include <cstring> +#include <iostream> +#include <string> +#include <stdio.h> +#include <sys/time.h> + +#include <apti18n.h> + /*}}}*/ + +using namespace std; + +// OpProgress::OpProgress - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +OpProgress::OpProgress() : Current(0), Total(0), Size(0), SubTotal(1), + LastPercent(0), Percent(0) +{ + memset(&LastTime,0,sizeof(LastTime)); +} + /*}}}*/ +// OpProgress::Progress - Sub progress with no state change /*{{{*/ +// --------------------------------------------------------------------- +/* Current is the Base Overall progress in units of Total. Cur is the sub + progress in units of SubTotal. Size is a scaling factor that says what + percent of Total SubTotal is. */ +void OpProgress::Progress(unsigned long long Cur) +{ + if (Total == 0 || Size == 0 || SubTotal == 0) + Percent = 0; + else + Percent = (Current + Cur/((double)SubTotal)*Size)*100.0/Total; + Update(); +} + /*}}}*/ +// OpProgress::OverallProgress - Set the overall progress /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void OpProgress::OverallProgress(unsigned long long Current, unsigned long long Total, + unsigned long long Size,const string &Op) +{ + this->Current = Current; + this->Total = Total; + this->Size = Size; + this->Op = Op; + SubOp = string(); + if (Total == 0) + Percent = 0; + else + Percent = Current*100.0/Total; + Update(); +} + /*}}}*/ +// OpProgress::SubProgress - Set the sub progress state /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void OpProgress::SubProgress(unsigned long long SubTotal,const string &Op, + float const Percent) +{ + this->SubTotal = SubTotal; + if (Op.empty() == false) + SubOp = Op; + if (Total == 0 || Percent == 0) + this->Percent = 0; + else if (Percent != -1) + this->Percent = this->Current += (Size*Percent)/SubTotal; + else + this->Percent = Current*100.0/Total; + Update(); +} + /*}}}*/ +// OpProgress::CheckChange - See if the display should be updated /*{{{*/ +// --------------------------------------------------------------------- +/* Progress calls are made so frequently that if every one resulted in + an update the display would be swamped and the system much slower. + This provides an upper bound on the update rate. */ +bool OpProgress::CheckChange(float Interval) +{ + // New major progress indication + if (Op != LastOp) + { + MajorChange = true; + LastOp = Op; + return true; + } + MajorChange = false; + + if (SubOp != LastSubOp) + { + LastSubOp = SubOp; + return true; + } + + if (std::lround(LastPercent) == std::lround(Percent)) + return false; + + LastPercent = Percent; + + if (Interval == 0) + return false; + + // Check time delta + auto const Now = std::chrono::steady_clock::now().time_since_epoch(); + auto const Now_sec = std::chrono::duration_cast<std::chrono::seconds>(Now); + auto const Now_usec = std::chrono::duration_cast<std::chrono::microseconds>(Now - Now_sec); + struct timeval NowTime = { Now_sec.count(), Now_usec.count() }; + + std::chrono::duration<decltype(Interval)> Delta = + std::chrono::seconds(NowTime.tv_sec - LastTime.tv_sec) + + std::chrono::microseconds(NowTime.tv_sec - LastTime.tv_usec); + + if (Delta.count() < Interval) + return false; + LastTime = NowTime; + return true; +} + /*}}}*/ +// OpTextProgress::OpTextProgress - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +OpTextProgress::OpTextProgress(Configuration &Config) : + NoUpdate(false), NoDisplay(false), LastLen(0) +{ + if (Config.FindI("quiet",0) >= 1 || Config.FindB("quiet::NoUpdate", false) == true) + NoUpdate = true; + if (Config.FindI("quiet",0) >= 2 || Config.FindB("quiet::NoProgress", false) == true) + NoDisplay = true; +} + /*}}}*/ +// OpTextProgress::Done - Clean up the display /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void OpTextProgress::Done() +{ + if (NoUpdate == false && OldOp.empty() == false) + { + char S[300]; + if (_error->PendingError() == true) + snprintf(S,sizeof(S),_("%c%s... Error!"),'\r',OldOp.c_str()); + else + snprintf(S,sizeof(S),_("%c%s... Done"),'\r',OldOp.c_str()); + Write(S); + cout << endl; + OldOp = string(); + } + + if (NoUpdate == true && NoDisplay == false && OldOp.empty() == false) + { + OldOp = string(); + cout << endl; + } +} + /*}}}*/ +// OpTextProgress::Update - Simple text spinner /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void OpTextProgress::Update() +{ + if (CheckChange((NoUpdate == true?0:0.7)) == false) + return; + + // No percent spinner + if (NoUpdate == true) + { + if (MajorChange == false) + return; + if (NoDisplay == false) + { + if (OldOp.empty() == false) + cout << endl; + OldOp = "a"; + cout << Op << _("...") << flush; + } + + return; + } + + // Erase the old text and 'log' the event + char S[300]; + if (MajorChange == true && OldOp.empty() == false) + { + snprintf(S,sizeof(S),"\r%s",OldOp.c_str()); + Write(S); + cout << endl; + } + + // Print the spinner + snprintf(S,sizeof(S),_("%c%s... %u%%"),'\r',Op.c_str(),(unsigned int)Percent); + Write(S); + + OldOp = Op; +} + /*}}}*/ +// OpTextProgress::Write - Write the progress string /*{{{*/ +// --------------------------------------------------------------------- +/* This space fills the end to overwrite the previous text */ +void OpTextProgress::Write(const char *S) +{ + cout << S; + for (unsigned int I = strlen(S); I < LastLen; I++) + cout << ' '; + cout << '\r' << flush; + LastLen = strlen(S); +} + /*}}}*/ diff --git a/apt-pkg/contrib/progress.h b/apt-pkg/contrib/progress.h new file mode 100644 index 0000000..4406a38 --- /dev/null +++ b/apt-pkg/contrib/progress.h @@ -0,0 +1,89 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + OpProgress - Operation Progress + + This class allows lengthy operations to communicate their progress + to the GUI. The progress model is simple and is not designed to handle + the complex case of the multi-activity acquire class. + + The model is based on the concept of an overall operation consisting + of a series of small sub operations. Each sub operation has it's own + completion status and the overall operation has it's completion status. + The units of the two are not mixed and are completely independent. + + The UI is expected to subclass this to provide the visuals to the user. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_PROGRESS_H +#define PKGLIB_PROGRESS_H + +#include <apt-pkg/macros.h> +#include <string> +#include <sys/time.h> + +#ifndef APT_8_CLEANER_HEADERS +using std::string; +#endif + +class Configuration; +class OpProgress +{ + unsigned long long Current; + unsigned long long Total; + unsigned long long Size; + unsigned long long SubTotal; + float LastPercent; + + // Change reduction code + struct timeval LastTime; + std::string LastOp; + std::string LastSubOp; + + protected: + + std::string Op; + std::string SubOp; + float Percent; + + bool MajorChange; + + bool CheckChange(float Interval = 0.7); + virtual void Update() {}; + + public: + + void Progress(unsigned long long Current); + void SubProgress(unsigned long long SubTotal, const std::string &Op = "", float const Percent = -1); + void OverallProgress(unsigned long long Current,unsigned long long Total, + unsigned long long Size,const std::string &Op); + virtual void Done() {}; + + OpProgress(); + virtual ~OpProgress() {}; +}; + +class OpTextProgress : public OpProgress +{ + protected: + + std::string OldOp; + bool NoUpdate; + bool NoDisplay; + unsigned long LastLen; + virtual void Update() APT_OVERRIDE; + void Write(const char *S); + + public: + + virtual void Done() APT_OVERRIDE; + + OpTextProgress(bool NoUpdate = false) : NoUpdate(NoUpdate), + NoDisplay(false), LastLen(0) {}; + OpTextProgress(Configuration &Config); + virtual ~OpTextProgress() {Done();}; +}; + +#endif diff --git a/apt-pkg/contrib/proxy.cc b/apt-pkg/contrib/proxy.cc new file mode 100644 index 0000000..6dc3b06 --- /dev/null +++ b/apt-pkg/contrib/proxy.cc @@ -0,0 +1,97 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Proxy - Proxy related functions + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/strutl.h> + +#include <algorithm> +#include <iostream> +#include <fcntl.h> +#include <unistd.h> + +#include "proxy.h" + /*}}}*/ + +// AutoDetectProxy - auto detect proxy /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static std::vector<std::string> CompatibleProxies(URI const &URL) +{ + if (URL.Access == "http" || URL.Access == "https") + return {"http", "https", "socks5h"}; + return {URL.Access}; +} + +bool AutoDetectProxy(URI &URL) +{ + // we support both http/https debug options + bool Debug = _config->FindB("Debug::Acquire::"+URL.Access,false); + + // the user already explicitly set a proxy for this host + if(_config->Find("Acquire::"+URL.Access+"::proxy::"+URL.Host, "") != "") + return true; + + // option is "Acquire::http::Proxy-Auto-Detect" but we allow the old + // name without the dash ("-") + std::string AutoDetectProxyCmd = _config->Find("Acquire::"+URL.Access+"::Proxy-Auto-Detect", + _config->Find("Acquire::"+URL.Access+"::ProxyAutoDetect")); + + if (AutoDetectProxyCmd.empty()) + return true; + + if (Debug) + std::clog << "Using auto proxy detect command: " << AutoDetectProxyCmd << std::endl; + + if (faccessat(AT_FDCWD, AutoDetectProxyCmd.c_str(), R_OK | X_OK, AT_EACCESS) != 0) + return _error->Errno("access", "ProxyAutoDetect command '%s' can not be executed!", AutoDetectProxyCmd.c_str()); + + std::string const urlstring = URL; + std::vector<const char *> Args; + Args.push_back(AutoDetectProxyCmd.c_str()); + Args.push_back(urlstring.c_str()); + Args.push_back(nullptr); + FileFd PipeFd; + pid_t Child; + if (Popen(&Args[0], PipeFd, Child, FileFd::ReadOnly, false, true) == false) + return _error->Error("ProxyAutoDetect command '%s' failed!", AutoDetectProxyCmd.c_str()); + char buf[512]; + bool const goodread = PipeFd.ReadLine(buf, sizeof(buf)) != nullptr; + PipeFd.Close(); + if (ExecWait(Child, "ProxyAutoDetect") == false) + return false; + // no output means the detector has no idea which proxy to use + // and apt will use the generic proxy settings + if (goodread == false) + return true; + auto const cleanedbuf = _strstrip(buf); + // We warn about this as the implementor probably meant to use DIRECT instead + if (cleanedbuf[0] == '\0') + { + _error->Warning("ProxyAutoDetect command returned an empty line"); + return true; + } + + if (Debug) + std::clog << "auto detect command returned: '" << cleanedbuf << "'" << std::endl; + + auto compatibleTypes = CompatibleProxies(URL); + bool compatible = strcmp(cleanedbuf, "DIRECT") == 0 || + compatibleTypes.end() != std::find_if(compatibleTypes.begin(), + compatibleTypes.end(), [cleanedbuf](std::string &compat) { + return strstr(cleanedbuf, compat.c_str()) == cleanedbuf; + }); + + if (compatible) + _config->Set("Acquire::"+URL.Access+"::proxy::"+URL.Host, cleanedbuf); + + return true; +} + /*}}}*/ diff --git a/apt-pkg/contrib/proxy.h b/apt-pkg/contrib/proxy.h new file mode 100644 index 0000000..2cbcd07 --- /dev/null +++ b/apt-pkg/contrib/proxy.h @@ -0,0 +1,16 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Proxy - Proxy operations + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_PROXY_H +#define PKGLIB_PROXY_H + +class URI; +bool AutoDetectProxy(URI &URL); + + +#endif diff --git a/apt-pkg/contrib/sha1.cc b/apt-pkg/contrib/sha1.cc new file mode 100644 index 0000000..bf0b9d6 --- /dev/null +++ b/apt-pkg/contrib/sha1.cc @@ -0,0 +1,273 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + SHA1 - SHA-1 Secure Hash Algorithm. + + This file is a Public Domain wrapper for the Public Domain SHA1 + calculation code that is at it's end. + + The algorithm was originally implemented by + Steve Reid <sreid@sea-to-sky.net> and later modified by + James H. Brown <jbrown@burgoyne.com>. + + Modifications for APT were done by Alfredo K. Kojima and Jason + Gunthorpe. + + Still in the public domain. + + Test Vectors (from FIPS PUB 180-1) + "abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 + A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F + + ##################################################################### + */ + /*}}} */ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/sha1.h> + +#include <stdint.h> +#include <string.h> + /*}}}*/ + +// SHA1Transform - Alters an existing SHA-1 hash /*{{{*/ +// --------------------------------------------------------------------- +/* The core of the SHA-1 algorithm. This alters an existing SHA-1 hash to + reflect the addition of 16 longwords of new data. Other routines convert + incoming stream data into 16 long word chunks for this routine */ + +#define rol(value,bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#ifndef WORDS_BIGENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1),R2,R3,R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +static void SHA1Transform(uint32_t state[5],uint8_t const buffer[64]) +{ + uint32_t a,b,c,d,e; + typedef union + { + uint8_t c[64]; + uint32_t l[16]; + } + CHAR64LONG16; + CHAR64LONG16 workspace, *block; + + block = &workspace; + memcpy(block,buffer,sizeof(workspace)); + + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e,0); + R0(e,a,b,c,d,1); + R0(d,e,a,b,c,2); + R0(c,d,e,a,b,3); + R0(b,c,d,e,a,4); + R0(a,b,c,d,e,5); + R0(e,a,b,c,d,6); + R0(d,e,a,b,c,7); + R0(c,d,e,a,b,8); + R0(b,c,d,e,a,9); + R0(a,b,c,d,e,10); + R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); + R0(c,d,e,a,b,13); + R0(b,c,d,e,a,14); + R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); + R1(d,e,a,b,c,17); + R1(c,d,e,a,b,18); + R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); + R2(e,a,b,c,d,21); + R2(d,e,a,b,c,22); + R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); + R2(a,b,c,d,e,25); + R2(e,a,b,c,d,26); + R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); + R2(b,c,d,e,a,29); + R2(a,b,c,d,e,30); + R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); + R2(c,d,e,a,b,33); + R2(b,c,d,e,a,34); + R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); + R2(d,e,a,b,c,37); + R2(c,d,e,a,b,38); + R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); + R3(e,a,b,c,d,41); + R3(d,e,a,b,c,42); + R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); + R3(a,b,c,d,e,45); + R3(e,a,b,c,d,46); + R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); + R3(b,c,d,e,a,49); + R3(a,b,c,d,e,50); + R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); + R3(c,d,e,a,b,53); + R3(b,c,d,e,a,54); + R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); + R3(d,e,a,b,c,57); + R3(c,d,e,a,b,58); + R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); + R4(e,a,b,c,d,61); + R4(d,e,a,b,c,62); + R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); + R4(a,b,c,d,e,65); + R4(e,a,b,c,d,66); + R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); + R4(b,c,d,e,a,69); + R4(a,b,c,d,e,70); + R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); + R4(c,d,e,a,b,73); + R4(b,c,d,e,a,74); + R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); + R4(d,e,a,b,c,77); + R4(c,d,e,a,b,78); + R4(b,c,d,e,a,79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; +} + /*}}}*/ + +// SHA1Summation::SHA1Summation - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +SHA1Summation::SHA1Summation() +{ + uint32_t *state = (uint32_t *)State; + uint32_t *count = (uint32_t *)Count; + + /* SHA1 initialization constants */ + state[0] = 0x67452301; + state[1] = 0xEFCDAB89; + state[2] = 0x98BADCFE; + state[3] = 0x10325476; + state[4] = 0xC3D2E1F0; + count[0] = 0; + count[1] = 0; + Done = false; +} + /*}}}*/ +// SHA1Summation::Result - Return checksum value /*{{{*/ +// --------------------------------------------------------------------- +/* Add() may not be called after this */ +SHA1SumValue SHA1Summation::Result() +{ + uint32_t *state = (uint32_t *)State; + uint32_t *count = (uint32_t *)Count; + + // Apply the padding + if (Done == false) + { + unsigned char finalcount[8]; + + for (unsigned i = 0; i < 8; i++) + { + // Endian independent + finalcount[i] = (unsigned char) ((count[(i >= 4 ? 0 : 1)] + >> ((3 - (i & 3)) * 8)) & 255); + } + + Add((unsigned char *) "\200",1); + while ((count[0] & 504) != 448) + Add((unsigned char *) "\0",1); + + Add(finalcount,8); /* Should cause a SHA1Transform() */ + + } + + Done = true; + + // Transfer over the result + SHA1SumValue Value; + unsigned char res[20]; + for (unsigned i = 0; i < 20; i++) + { + res[i] = (unsigned char) + ((state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + Value.Set(res); + return Value; +} + /*}}}*/ +// SHA1Summation::Add - Adds content of buffer into the checksum /*{{{*/ +// --------------------------------------------------------------------- +/* May not be called after Result() is called */ +bool SHA1Summation::Add(const unsigned char *data,unsigned long long len) +{ + if (Done) + return false; + if (len == 0) + return true; + + uint32_t *state = (uint32_t *)State; + uint32_t *count = (uint32_t *)Count; + uint8_t *buffer = (uint8_t *)Buffer; + uint32_t i,j; + + j = (count[0] >> 3) & 63; + if ((count[0] += len << 3) < (len << 3)) + count[1]++; + count[1] += (len >> 29); + if ((j + len) > 63) + { + memcpy(&buffer[j],data,(i = 64 - j)); + SHA1Transform(state,buffer); + for (; i + 63 < len; i += 64) + { + SHA1Transform(state,&data[i]); + } + j = 0; + } + else + i = 0; + memcpy(&buffer[j],&data[i],len - i); + + return true; +} + /*}}}*/ diff --git a/apt-pkg/contrib/sha1.h b/apt-pkg/contrib/sha1.h new file mode 100644 index 0000000..dffb950 --- /dev/null +++ b/apt-pkg/contrib/sha1.h @@ -0,0 +1,47 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + SHA1SumValue - Storage for a SHA-1 hash. + SHA1Summation - SHA-1 Secure Hash Algorithm. + + This is a C++ interface to a set of SHA1Sum functions, that mirrors + the equivalent MD5 classes. + + ##################################################################### */ + /*}}}*/ +#ifndef APTPKG_SHA1_H +#define APTPKG_SHA1_H + +#include "hashsum_template.h" + +#ifndef APT_10_CLEANER_HEADERS +#include <algorithm> +#include <cstring> +#include <string> +#endif +#ifndef APT_8_CLEANER_HEADERS +using std::string; +using std::min; +#endif + +typedef HashSumValue<160> SHA1SumValue; + +class SHA1Summation : public SummationImplementation +{ + /* assumes 64-bit alignment just in case */ + unsigned char Buffer[64] __attribute__((aligned(8))); + unsigned char State[5*4] __attribute__((aligned(8))); + unsigned char Count[2*4] __attribute__((aligned(8))); + bool Done; + + public: + bool Add(const unsigned char *inbuf, unsigned long long inlen) APT_OVERRIDE APT_NONNULL(2); + using SummationImplementation::Add; + + SHA1SumValue Result(); + + SHA1Summation(); +}; + +#endif diff --git a/apt-pkg/contrib/sha2.h b/apt-pkg/contrib/sha2.h new file mode 100644 index 0000000..e1a8c6c --- /dev/null +++ b/apt-pkg/contrib/sha2.h @@ -0,0 +1,108 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + SHA{512,256}SumValue - Storage for a SHA-{512,256} hash. + SHA{512,256}Summation - SHA-{512,256} Secure Hash Algorithm. + + This is a C++ interface to a set of SHA{512,256}Sum functions, that mirrors + the equivalent MD5 & SHA1 classes. + + ##################################################################### */ + /*}}}*/ +#ifndef APTPKG_SHA2_H +#define APTPKG_SHA2_H + +#include <cstring> + +#include "hashsum_template.h" +#include "sha2_internal.h" + +#ifndef APT_10_CLEANER_HEADERS +#include <algorithm> +#include <string> +#include <stdint.h> +#endif + + +typedef HashSumValue<512> SHA512SumValue; +typedef HashSumValue<256> SHA256SumValue; + +class SHA2SummationBase : public SummationImplementation +{ + protected: + bool Done; + public: + bool Add(const unsigned char *inbuf, unsigned long long len) APT_OVERRIDE APT_NONNULL(2) = 0; + + void Result(); +}; + +class SHA256Summation : public SHA2SummationBase +{ + SHA256_CTX ctx; + unsigned char Sum[32]; + + public: + bool Add(const unsigned char *inbuf, unsigned long long len) APT_OVERRIDE APT_NONNULL(2) + { + if (Done) + return false; + SHA256_Update(&ctx, inbuf, len); + return true; + }; + using SummationImplementation::Add; + + SHA256SumValue Result() + { + if (!Done) { + SHA256_Final(Sum, &ctx); + Done = true; + } + SHA256SumValue res; + res.Set(Sum); + return res; + }; + SHA256Summation() + { + SHA256_Init(&ctx); + Done = false; + memset(&Sum, 0, sizeof(Sum)); + }; +}; + +class SHA512Summation : public SHA2SummationBase +{ + SHA512_CTX ctx; + unsigned char Sum[64]; + + public: + bool Add(const unsigned char *inbuf, unsigned long long len) APT_OVERRIDE APT_NONNULL(2) + { + if (Done) + return false; + SHA512_Update(&ctx, inbuf, len); + return true; + }; + using SummationImplementation::Add; + + SHA512SumValue Result() + { + if (!Done) { + SHA512_Final(Sum, &ctx); + Done = true; + } + SHA512SumValue res; + res.Set(Sum); + return res; + }; + SHA512Summation() + { + SHA512_Init(&ctx); + Done = false; + memset(&Sum, 0, sizeof(Sum)); + }; +}; + + +#endif diff --git a/apt-pkg/contrib/sha256.h b/apt-pkg/contrib/sha256.h new file mode 100644 index 0000000..15146c9 --- /dev/null +++ b/apt-pkg/contrib/sha256.h @@ -0,0 +1,8 @@ +#ifndef APTPKG_SHA256_H +#define APTPKG_SHA256_H + +#include "sha2.h" + +#warning "This header is deprecated, please include sha2.h instead" + +#endif diff --git a/apt-pkg/contrib/sha2_internal.cc b/apt-pkg/contrib/sha2_internal.cc new file mode 100644 index 0000000..92a4a86 --- /dev/null +++ b/apt-pkg/contrib/sha2_internal.cc @@ -0,0 +1,1089 @@ +/* + * FILE: sha2.c + * AUTHOR: Aaron D. Gifford - http://www.aarongifford.com/ + * + * Copyright (c) 2000-2001, Aaron D. Gifford + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ +#include <config.h> + +#include "sha2_internal.h" +#include <assert.h> /* assert() */ +#include <endian.h> +#include <string.h> /* memcpy()/memset() or bcopy()/bzero() */ + +/* + * ASSERT NOTE: + * Some sanity checking code is included using assert(). On my FreeBSD + * system, this additional code can be removed by compiling with NDEBUG + * defined. Check your own systems manpage on assert() to see how to + * compile WITHOUT the sanity checking code on your system. + * + * UNROLLED TRANSFORM LOOP NOTE: + * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform + * loop version for the hash transform rounds (defined using macros + * later in this file). Either define on the command line, for example: + * + * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c + * + * or define below: + * + * #define SHA2_UNROLL_TRANSFORM + * + */ + + +/*** SHA-256/384/512 Machine Architecture Definitions *****************/ +/* + * BYTE_ORDER NOTE: + * + * Please make sure that your system defines BYTE_ORDER. If your + * architecture is little-endian, make sure it also defines + * LITTLE_ENDIAN and that the two (BYTE_ORDER and LITTLE_ENDIAN) are + * equivalent. + * + * If your system does not define the above, then you can do so by + * hand like this: + * + * #define LITTLE_ENDIAN 1234 + * #define BIG_ENDIAN 4321 + * + * And for little-endian machines, add: + * + * #define BYTE_ORDER LITTLE_ENDIAN + * + * Or for big-endian machines: + * + * #define BYTE_ORDER BIG_ENDIAN + * + * The FreeBSD machine this was written on defines BYTE_ORDER + * appropriately by including <sys/types.h> (which in turn includes + * <machine/endian.h> where the appropriate definitions are actually + * made). + */ +#if !defined(BYTE_ORDER) || (BYTE_ORDER != LITTLE_ENDIAN && BYTE_ORDER != BIG_ENDIAN) +#error Define BYTE_ORDER to be equal to either LITTLE_ENDIAN or BIG_ENDIAN +#endif + +/* + * Define the followingsha2_* types to types of the correct length on + * the native architecture. Most BSD systems and Linux define u_intXX_t + * types. Machines with very recent ANSI C headers, can use the + * uintXX_t definintions from inttypes.h by defining SHA2_USE_INTTYPES_H + * during compile or in the sha.h header file. + * + * Machines that support neither u_intXX_t nor inttypes.h's uintXX_t + * will need to define these three typedefs below (and the appropriate + * ones in sha.h too) by hand according to their system architecture. + * + * Thank you, Jun-ichiro itojun Hagino, for suggesting using u_intXX_t + * types and pointing out recent ANSI C support for uintXX_t in inttypes.h. + */ +#ifdef SHA2_USE_INTTYPES_H + +typedef uint8_t sha2_byte; /* Exactly 1 byte */ +typedef uint32_t sha2_word32; /* Exactly 4 bytes */ +typedef uint64_t sha2_word64; /* Exactly 8 bytes */ + +#else /* SHA2_USE_INTTYPES_H */ + +typedef u_int8_t sha2_byte; /* Exactly 1 byte */ +typedef u_int32_t sha2_word32; /* Exactly 4 bytes */ +typedef u_int64_t sha2_word64; /* Exactly 8 bytes */ + +#endif /* SHA2_USE_INTTYPES_H */ + + +/*** SHA-256/384/512 Various Length Definitions ***********************/ +/* NOTE: Most of these are in sha2.h */ +#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8) +#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16) +#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16) + + +/*** ENDIAN REVERSAL MACROS *******************************************/ +#if BYTE_ORDER == LITTLE_ENDIAN +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) +#define REVERSE32(w,x) { \ + (x) = __builtin_bswap32(w); \ +} +#define REVERSE64(w,x) { \ + (x) = __builtin_bswap64(w); \ +} +#else +#define REVERSE32(w,x) { \ + sha2_word32 tmp = (w); \ + tmp = (tmp >> 16) | (tmp << 16); \ + (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \ +} +#define REVERSE64(w,x) { \ + sha2_word64 tmp = (w); \ + tmp = (tmp >> 32) | (tmp << 32); \ + tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \ + ((tmp & 0x00ff00ff00ff00ffULL) << 8); \ + (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \ + ((tmp & 0x0000ffff0000ffffULL) << 16); \ +} +#endif +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + +/* + * Macro for incrementally adding the unsigned 64-bit integer n to the + * unsigned 128-bit integer (represented using a two-element array of + * 64-bit words): + */ +#define ADDINC128(w,n) { \ + (w)[0] += (sha2_word64)(n); \ + if ((w)[0] < (n)) { \ + (w)[1]++; \ + } \ +} + +/* + * Macros for copying blocks of memory and for zeroing out ranges + * of memory. Using these macros makes it easy to switch from + * using memset()/memcpy() and using bzero()/bcopy(). + * + * Please define either SHA2_USE_MEMSET_MEMCPY or define + * SHA2_USE_BZERO_BCOPY depending on which function set you + * choose to use: + */ +#if !defined(SHA2_USE_MEMSET_MEMCPY) && !defined(SHA2_USE_BZERO_BCOPY) +/* Default to memset()/memcpy() if no option is specified */ +#define SHA2_USE_MEMSET_MEMCPY 1 +#endif +#if defined(SHA2_USE_MEMSET_MEMCPY) && defined(SHA2_USE_BZERO_BCOPY) +/* Abort with an error if BOTH options are defined */ +#error Define either SHA2_USE_MEMSET_MEMCPY or SHA2_USE_BZERO_BCOPY, not both! +#endif + +#ifdef SHA2_USE_MEMSET_MEMCPY +#define MEMSET_BZERO(p,l) memset((p), 0, (l)) +#define MEMCPY_BCOPY(d,s,l) memcpy((d), (s), (l)) +#endif +#ifdef SHA2_USE_BZERO_BCOPY +#define MEMSET_BZERO(p,l) bzero((p), (l)) +#define MEMCPY_BCOPY(d,s,l) bcopy((s), (d), (l)) +#endif + + +/*** THE SIX LOGICAL FUNCTIONS ****************************************/ +/* + * Bit shifting and rotation (used by the six SHA-XYZ logical functions: + * + * NOTE: The naming of R and S appears backwards here (R is a SHIFT and + * S is a ROTATION) because the SHA-256/384/512 description document + * (see http://csrc.nist.gov/cryptval/shs/sha256-384-512.pdf) uses this + * same "backwards" definition. + */ +/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */ +#define R(b,x) ((x) >> (b)) +/* 32-bit Rotate-right (used in SHA-256): */ +#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b)))) +/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */ +#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b)))) + +/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */ +#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) +#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) + +/* Four of six logical functions used in SHA-256: */ +#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x))) +#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x))) +#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x))) +#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x))) + +/* Four of six logical functions used in SHA-384 and SHA-512: */ +#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x))) +#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x))) +#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x))) +#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x))) + +/*** INTERNAL FUNCTION PROTOTYPES *************************************/ +/* NOTE: These should not be accessed directly from outside this + * library -- they are intended for private internal visibility/use + * only. + */ +static void SHA512_Last(SHA512_CTX*); +static void SHA256_Transform(SHA256_CTX*, const sha2_word32*); +static void SHA512_Transform(SHA512_CTX*, const sha2_word64*); + + +/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/ +/* Hash constant words K for SHA-256: */ +const static sha2_word32 K256[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, + 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, + 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, + 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, + 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, + 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, + 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, + 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, + 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, + 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, + 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL +}; + +/* Initial hash value H for SHA-256: */ +const static sha2_word32 sha256_initial_hash_value[8] = { + 0x6a09e667UL, + 0xbb67ae85UL, + 0x3c6ef372UL, + 0xa54ff53aUL, + 0x510e527fUL, + 0x9b05688cUL, + 0x1f83d9abUL, + 0x5be0cd19UL +}; + +/* Hash constant words K for SHA-384 and SHA-512: */ +const static sha2_word64 K512[80] = { + 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, + 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, + 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, + 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, + 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, + 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, + 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, + 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, + 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, + 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, + 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, + 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, + 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, + 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, + 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, + 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, + 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, + 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, + 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, + 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, + 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, + 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, + 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, + 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, + 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, + 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, + 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, + 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, + 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, + 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, + 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, + 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, + 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, + 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, + 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, + 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, + 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, + 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, + 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, + 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL +}; + +/* Initial hash value H for SHA-384 */ +const static sha2_word64 sha384_initial_hash_value[8] = { + 0xcbbb9d5dc1059ed8ULL, + 0x629a292a367cd507ULL, + 0x9159015a3070dd17ULL, + 0x152fecd8f70e5939ULL, + 0x67332667ffc00b31ULL, + 0x8eb44a8768581511ULL, + 0xdb0c2e0d64f98fa7ULL, + 0x47b5481dbefa4fa4ULL +}; + +/* Initial hash value H for SHA-512 */ +const static sha2_word64 sha512_initial_hash_value[8] = { + 0x6a09e667f3bcc908ULL, + 0xbb67ae8584caa73bULL, + 0x3c6ef372fe94f82bULL, + 0xa54ff53a5f1d36f1ULL, + 0x510e527fade682d1ULL, + 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, + 0x5be0cd19137e2179ULL +}; + +/* + * Constant used by SHA256/384/512_End() functions for converting the + * digest to a readable hexadecimal character string: + */ +static const char *sha2_hex_digits = "0123456789abcdef"; + + +/*** SHA-256: *********************************************************/ +void SHA256_Init(SHA256_CTX* context) { + if (context == (SHA256_CTX*)0) { + return; + } + MEMCPY_BCOPY(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH); + MEMSET_BZERO(context->buffer, SHA256_BLOCK_LENGTH); + context->bitcount = 0; +} + +#ifdef SHA2_UNROLL_TRANSFORM + +/* Unrolled SHA-256 round macros: */ + +#if BYTE_ORDER == LITTLE_ENDIAN + +#define ROUND256_0_TO_15(a,b,c,d,e,f,g,h) \ + REVERSE32(*data++, W256[j]); \ + T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + \ + K256[j] + W256[j]; \ + (d) += T1; \ + (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ + j++ + + +#else /* BYTE_ORDER == LITTLE_ENDIAN */ + +#define ROUND256_0_TO_15(a,b,c,d,e,f,g,h) \ + T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + \ + K256[j] + (W256[j] = *data++); \ + (d) += T1; \ + (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ + j++ + +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + +#define ROUND256(a,b,c,d,e,f,g,h) \ + s0 = W256[(j+1)&0x0f]; \ + s0 = sigma0_256(s0); \ + s1 = W256[(j+14)&0x0f]; \ + s1 = sigma1_256(s1); \ + T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + K256[j] + \ + (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); \ + (d) += T1; \ + (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ + j++ + +static void SHA256_Transform(SHA256_CTX* context, const sha2_word32* data) { + sha2_word32 a, b, c, d, e, f, g, h, s0, s1; + sha2_word32 T1, *W256; + int j; + + W256 = (sha2_word32*)context->buffer; + + /* Initialize registers with the prev. intermediate value */ + a = context->state[0]; + b = context->state[1]; + c = context->state[2]; + d = context->state[3]; + e = context->state[4]; + f = context->state[5]; + g = context->state[6]; + h = context->state[7]; + + j = 0; + do { + /* Rounds 0 to 15 (unrolled): */ + ROUND256_0_TO_15(a,b,c,d,e,f,g,h); + ROUND256_0_TO_15(h,a,b,c,d,e,f,g); + ROUND256_0_TO_15(g,h,a,b,c,d,e,f); + ROUND256_0_TO_15(f,g,h,a,b,c,d,e); + ROUND256_0_TO_15(e,f,g,h,a,b,c,d); + ROUND256_0_TO_15(d,e,f,g,h,a,b,c); + ROUND256_0_TO_15(c,d,e,f,g,h,a,b); + ROUND256_0_TO_15(b,c,d,e,f,g,h,a); + } while (j < 16); + + /* Now for the remaining rounds to 64: */ + do { + ROUND256(a,b,c,d,e,f,g,h); + ROUND256(h,a,b,c,d,e,f,g); + ROUND256(g,h,a,b,c,d,e,f); + ROUND256(f,g,h,a,b,c,d,e); + ROUND256(e,f,g,h,a,b,c,d); + ROUND256(d,e,f,g,h,a,b,c); + ROUND256(c,d,e,f,g,h,a,b); + ROUND256(b,c,d,e,f,g,h,a); + } while (j < 64); + + /* Compute the current intermediate hash value */ + context->state[0] += a; + context->state[1] += b; + context->state[2] += c; + context->state[3] += d; + context->state[4] += e; + context->state[5] += f; + context->state[6] += g; + context->state[7] += h; + + /* Clean up */ + a = b = c = d = e = f = g = h = T1 = 0; +} + +#else /* SHA2_UNROLL_TRANSFORM */ + +static void SHA256_Transform(SHA256_CTX* context, const sha2_word32* data) { + sha2_word32 a, b, c, d, e, f, g, h, s0, s1; + sha2_word32 T1, T2, *W256; + int j; + + W256 = (sha2_word32*)context->buffer; + + /* Initialize registers with the prev. intermediate value */ + a = context->state[0]; + b = context->state[1]; + c = context->state[2]; + d = context->state[3]; + e = context->state[4]; + f = context->state[5]; + g = context->state[6]; + h = context->state[7]; + + j = 0; + do { +#if BYTE_ORDER == LITTLE_ENDIAN + /* Copy data while converting to host byte order */ + REVERSE32(*data++,W256[j]); + /* Apply the SHA-256 compression function to update a..h */ + T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + W256[j]; +#else /* BYTE_ORDER == LITTLE_ENDIAN */ + /* Apply the SHA-256 compression function to update a..h with copy */ + T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + (W256[j] = *data++); +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + T2 = Sigma0_256(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 16); + + do { + /* Part of the message block expansion: */ + s0 = W256[(j+1)&0x0f]; + s0 = sigma0_256(s0); + s1 = W256[(j+14)&0x0f]; + s1 = sigma1_256(s1); + + /* Apply the SHA-256 compression function to update a..h */ + T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + + (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); + T2 = Sigma0_256(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 64); + + /* Compute the current intermediate hash value */ + context->state[0] += a; + context->state[1] += b; + context->state[2] += c; + context->state[3] += d; + context->state[4] += e; + context->state[5] += f; + context->state[6] += g; + context->state[7] += h; + + /* Clean up */ + a = b = c = d = e = f = g = h = T1 = T2 = 0; +} + +#endif /* SHA2_UNROLL_TRANSFORM */ + +void SHA256_Update(SHA256_CTX* context, const sha2_byte *data, size_t len) { + unsigned int freespace, usedspace; + + if (len == 0) { + /* Calling with no data is valid - we do nothing */ + return; + } + + /* Sanity check: */ + assert(context != (SHA256_CTX*)0 && data != (sha2_byte*)0); + + usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH; + if (usedspace > 0) { + /* Calculate how much free space is available in the buffer */ + freespace = SHA256_BLOCK_LENGTH - usedspace; + + if (len >= freespace) { + /* Fill the buffer completely and process it */ + MEMCPY_BCOPY(&context->buffer[usedspace], data, freespace); + context->bitcount += freespace << 3; + len -= freespace; + data += freespace; + SHA256_Transform(context, (sha2_word32*)context->buffer); + } else { + /* The buffer is not yet full */ + MEMCPY_BCOPY(&context->buffer[usedspace], data, len); + context->bitcount += len << 3; + /* Clean up: */ + usedspace = freespace = 0; + return; + } + } + while (len >= SHA256_BLOCK_LENGTH) { + /* Process as many complete blocks as we can */ + sha2_byte buffer[SHA256_BLOCK_LENGTH]; + MEMCPY_BCOPY(buffer, data, SHA256_BLOCK_LENGTH); + SHA256_Transform(context, (sha2_word32*)buffer); + context->bitcount += SHA256_BLOCK_LENGTH << 3; + len -= SHA256_BLOCK_LENGTH; + data += SHA256_BLOCK_LENGTH; + } + if (len > 0) { + /* There's left-overs, so save 'em */ + MEMCPY_BCOPY(context->buffer, data, len); + context->bitcount += len << 3; + } + /* Clean up: */ + usedspace = freespace = 0; +} + +void SHA256_Final(sha2_byte digest[], SHA256_CTX* context) { + sha2_word32 *d = (sha2_word32*)digest; + unsigned int usedspace; + + /* Sanity check: */ + assert(context != (SHA256_CTX*)0); + + /* If no digest buffer is passed, we don't bother doing this: */ + if (digest != (sha2_byte*)0) { + usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH; +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert FROM host byte order */ + REVERSE64(context->bitcount,context->bitcount); +#endif + if (usedspace > 0) { + /* Begin padding with a 1 bit: */ + context->buffer[usedspace++] = 0x80; + + if (usedspace <= SHA256_SHORT_BLOCK_LENGTH) { + /* Set-up for the last transform: */ + MEMSET_BZERO(&context->buffer[usedspace], SHA256_SHORT_BLOCK_LENGTH - usedspace); + } else { + if (usedspace < SHA256_BLOCK_LENGTH) { + MEMSET_BZERO(&context->buffer[usedspace], SHA256_BLOCK_LENGTH - usedspace); + } + /* Do second-to-last transform: */ + SHA256_Transform(context, (sha2_word32*)context->buffer); + + /* And set-up for the last transform: */ + MEMSET_BZERO(context->buffer, SHA256_SHORT_BLOCK_LENGTH); + } + } else { + /* Set-up for the last transform: */ + MEMSET_BZERO(context->buffer, SHA256_SHORT_BLOCK_LENGTH); + + /* Begin padding with a 1 bit: */ + *context->buffer = 0x80; + } + /* Set the bit count: */ + union { + sha2_byte* c; + sha2_word64* l; + } bitcount; + bitcount.c = &context->buffer[SHA256_SHORT_BLOCK_LENGTH]; + *(bitcount.l) = context->bitcount; + + /* Final transform: */ + SHA256_Transform(context, (sha2_word32*)context->buffer); + +#if BYTE_ORDER == LITTLE_ENDIAN + { + /* Convert TO host byte order */ + int j; + for (j = 0; j < 8; j++) { + REVERSE32(context->state[j],context->state[j]); + *d++ = context->state[j]; + } + } +#else + MEMCPY_BCOPY(d, context->state, SHA256_DIGEST_LENGTH); +#endif + } + + /* Clean up state data: */ + MEMSET_BZERO(context, sizeof(*context)); + usedspace = 0; +} + +char *SHA256_End(SHA256_CTX* context, char buffer[]) { + sha2_byte digest[SHA256_DIGEST_LENGTH], *d = digest; + int i; + + /* Sanity check: */ + assert(context != (SHA256_CTX*)0); + + if (buffer != (char*)0) { + SHA256_Final(digest, context); + + for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { + *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; + *buffer++ = sha2_hex_digits[*d & 0x0f]; + d++; + } + *buffer = (char)0; + } else { + MEMSET_BZERO(context, sizeof(*context)); + } + MEMSET_BZERO(digest, SHA256_DIGEST_LENGTH); + return buffer; +} + +char* SHA256_Data(const sha2_byte* data, size_t len, char digest[SHA256_DIGEST_STRING_LENGTH]) { + SHA256_CTX context; + + SHA256_Init(&context); + SHA256_Update(&context, data, len); + return SHA256_End(&context, digest); +} + + +/*** SHA-512: *********************************************************/ +void SHA512_Init(SHA512_CTX* context) { + if (context == (SHA512_CTX*)0) { + return; + } + MEMCPY_BCOPY(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH); + MEMSET_BZERO(context->buffer, SHA512_BLOCK_LENGTH); + context->bitcount[0] = context->bitcount[1] = 0; +} + +#ifdef SHA2_UNROLL_TRANSFORM + +/* Unrolled SHA-512 round macros: */ +#if BYTE_ORDER == LITTLE_ENDIAN + +#define ROUND512_0_TO_15(a,b,c,d,e,f,g,h) \ + REVERSE64(*data++, W512[j]); \ + T1 = (h) + Sigma1_512(e) + Ch((e), (f), (g)) + \ + K512[j] + W512[j]; \ + (d) += T1, \ + (h) = T1 + Sigma0_512(a) + Maj((a), (b), (c)), \ + j++ + + +#else /* BYTE_ORDER == LITTLE_ENDIAN */ + +#define ROUND512_0_TO_15(a,b,c,d,e,f,g,h) \ + T1 = (h) + Sigma1_512(e) + Ch((e), (f), (g)) + \ + K512[j] + (W512[j] = *data++); \ + (d) += T1; \ + (h) = T1 + Sigma0_512(a) + Maj((a), (b), (c)); \ + j++ + +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + +#define ROUND512(a,b,c,d,e,f,g,h) \ + s0 = W512[(j+1)&0x0f]; \ + s0 = sigma0_512(s0); \ + s1 = W512[(j+14)&0x0f]; \ + s1 = sigma1_512(s1); \ + T1 = (h) + Sigma1_512(e) + Ch((e), (f), (g)) + K512[j] + \ + (W512[j&0x0f] += s1 + W512[(j+9)&0x0f] + s0); \ + (d) += T1; \ + (h) = T1 + Sigma0_512(a) + Maj((a), (b), (c)); \ + j++ + +static void SHA512_Transform(SHA512_CTX* context, const sha2_word64* data) { + sha2_word64 a, b, c, d, e, f, g, h, s0, s1; + sha2_word64 T1, *W512 = (sha2_word64*)context->buffer; + int j; + + /* Initialize registers with the prev. intermediate value */ + a = context->state[0]; + b = context->state[1]; + c = context->state[2]; + d = context->state[3]; + e = context->state[4]; + f = context->state[5]; + g = context->state[6]; + h = context->state[7]; + + j = 0; + do { + ROUND512_0_TO_15(a,b,c,d,e,f,g,h); + ROUND512_0_TO_15(h,a,b,c,d,e,f,g); + ROUND512_0_TO_15(g,h,a,b,c,d,e,f); + ROUND512_0_TO_15(f,g,h,a,b,c,d,e); + ROUND512_0_TO_15(e,f,g,h,a,b,c,d); + ROUND512_0_TO_15(d,e,f,g,h,a,b,c); + ROUND512_0_TO_15(c,d,e,f,g,h,a,b); + ROUND512_0_TO_15(b,c,d,e,f,g,h,a); + } while (j < 16); + + /* Now for the remaining rounds up to 79: */ + do { + ROUND512(a,b,c,d,e,f,g,h); + ROUND512(h,a,b,c,d,e,f,g); + ROUND512(g,h,a,b,c,d,e,f); + ROUND512(f,g,h,a,b,c,d,e); + ROUND512(e,f,g,h,a,b,c,d); + ROUND512(d,e,f,g,h,a,b,c); + ROUND512(c,d,e,f,g,h,a,b); + ROUND512(b,c,d,e,f,g,h,a); + } while (j < 80); + + /* Compute the current intermediate hash value */ + context->state[0] += a; + context->state[1] += b; + context->state[2] += c; + context->state[3] += d; + context->state[4] += e; + context->state[5] += f; + context->state[6] += g; + context->state[7] += h; + + /* Clean up */ + a = b = c = d = e = f = g = h = T1 = 0; +} + +#else /* SHA2_UNROLL_TRANSFORM */ + +static void SHA512_Transform(SHA512_CTX* context, const sha2_word64* data) { + sha2_word64 a, b, c, d, e, f, g, h, s0, s1; + sha2_word64 T1, T2, *W512 = (sha2_word64*)context->buffer; + int j; + + /* Initialize registers with the prev. intermediate value */ + a = context->state[0]; + b = context->state[1]; + c = context->state[2]; + d = context->state[3]; + e = context->state[4]; + f = context->state[5]; + g = context->state[6]; + h = context->state[7]; + + j = 0; + do { +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + REVERSE64(*data++, W512[j]); + /* Apply the SHA-512 compression function to update a..h */ + T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + W512[j]; +#else /* BYTE_ORDER == LITTLE_ENDIAN */ + /* Apply the SHA-512 compression function to update a..h with copy */ + T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + (W512[j] = *data++); +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + T2 = Sigma0_512(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 16); + + do { + /* Part of the message block expansion: */ + s0 = W512[(j+1)&0x0f]; + s0 = sigma0_512(s0); + s1 = W512[(j+14)&0x0f]; + s1 = sigma1_512(s1); + + /* Apply the SHA-512 compression function to update a..h */ + T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + + (W512[j&0x0f] += s1 + W512[(j+9)&0x0f] + s0); + T2 = Sigma0_512(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 80); + + /* Compute the current intermediate hash value */ + context->state[0] += a; + context->state[1] += b; + context->state[2] += c; + context->state[3] += d; + context->state[4] += e; + context->state[5] += f; + context->state[6] += g; + context->state[7] += h; + + /* Clean up */ + a = b = c = d = e = f = g = h = T1 = T2 = 0; +} + +#endif /* SHA2_UNROLL_TRANSFORM */ + +void SHA512_Update(SHA512_CTX* context, const sha2_byte *data, size_t len) { + unsigned int freespace, usedspace; + + if (len == 0) { + /* Calling with no data is valid - we do nothing */ + return; + } + + /* Sanity check: */ + assert(context != (SHA512_CTX*)0 && data != (sha2_byte*)0); + + usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH; + if (usedspace > 0) { + /* Calculate how much free space is available in the buffer */ + freespace = SHA512_BLOCK_LENGTH - usedspace; + + if (len >= freespace) { + /* Fill the buffer completely and process it */ + MEMCPY_BCOPY(&context->buffer[usedspace], data, freespace); + ADDINC128(context->bitcount, freespace << 3); + len -= freespace; + data += freespace; + SHA512_Transform(context, (sha2_word64*)context->buffer); + } else { + /* The buffer is not yet full */ + MEMCPY_BCOPY(&context->buffer[usedspace], data, len); + ADDINC128(context->bitcount, len << 3); + /* Clean up: */ + usedspace = freespace = 0; + return; + } + } + while (len >= SHA512_BLOCK_LENGTH) { + /* Process as many complete blocks as we can */ + sha2_byte buffer[SHA512_BLOCK_LENGTH]; + MEMCPY_BCOPY(buffer, data, SHA512_BLOCK_LENGTH); + SHA512_Transform(context, (sha2_word64*)buffer); + ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3); + len -= SHA512_BLOCK_LENGTH; + data += SHA512_BLOCK_LENGTH; + } + if (len > 0) { + /* There's left-overs, so save 'em */ + MEMCPY_BCOPY(context->buffer, data, len); + ADDINC128(context->bitcount, len << 3); + } + /* Clean up: */ + usedspace = freespace = 0; +} + +static void SHA512_Last(SHA512_CTX* context) { + unsigned int usedspace; + + usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH; +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert FROM host byte order */ + REVERSE64(context->bitcount[0],context->bitcount[0]); + REVERSE64(context->bitcount[1],context->bitcount[1]); +#endif + if (usedspace > 0) { + /* Begin padding with a 1 bit: */ + context->buffer[usedspace++] = 0x80; + + if (usedspace <= SHA512_SHORT_BLOCK_LENGTH) { + /* Set-up for the last transform: */ + MEMSET_BZERO(&context->buffer[usedspace], SHA512_SHORT_BLOCK_LENGTH - usedspace); + } else { + if (usedspace < SHA512_BLOCK_LENGTH) { + MEMSET_BZERO(&context->buffer[usedspace], SHA512_BLOCK_LENGTH - usedspace); + } + /* Do second-to-last transform: */ + SHA512_Transform(context, (sha2_word64*)context->buffer); + + /* And set-up for the last transform: */ + MEMSET_BZERO(context->buffer, SHA512_BLOCK_LENGTH - 2); + } + } else { + /* Prepare for final transform: */ + MEMSET_BZERO(context->buffer, SHA512_SHORT_BLOCK_LENGTH); + + /* Begin padding with a 1 bit: */ + *context->buffer = 0x80; + } + /* Store the length of input data (in bits): */ + union { + sha2_byte* c; + sha2_word64* l; + } bitcount; + bitcount.c = &context->buffer[SHA512_SHORT_BLOCK_LENGTH]; + bitcount.l[0] = context->bitcount[1]; + bitcount.l[1] = context->bitcount[0]; + + /* Final transform: */ + SHA512_Transform(context, (sha2_word64*)context->buffer); +} + +void SHA512_Final(sha2_byte digest[], SHA512_CTX* context) { + sha2_word64 *d = (sha2_word64*)digest; + + /* Sanity check: */ + assert(context != (SHA512_CTX*)0); + + /* If no digest buffer is passed, we don't bother doing this: */ + if (digest != (sha2_byte*)0) { + SHA512_Last(context); + + /* Save the hash data for output: */ +#if BYTE_ORDER == LITTLE_ENDIAN + { + /* Convert TO host byte order */ + int j; + for (j = 0; j < 8; j++) { + REVERSE64(context->state[j],context->state[j]); + *d++ = context->state[j]; + } + } +#else + MEMCPY_BCOPY(d, context->state, SHA512_DIGEST_LENGTH); +#endif + } + + /* Zero out state data */ + MEMSET_BZERO(context, sizeof(*context)); +} + +char *SHA512_End(SHA512_CTX* context, char buffer[]) { + sha2_byte digest[SHA512_DIGEST_LENGTH], *d = digest; + int i; + + /* Sanity check: */ + assert(context != (SHA512_CTX*)0); + + if (buffer != (char*)0) { + SHA512_Final(digest, context); + + for (i = 0; i < SHA512_DIGEST_LENGTH; i++) { + *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; + *buffer++ = sha2_hex_digits[*d & 0x0f]; + d++; + } + *buffer = (char)0; + } else { + MEMSET_BZERO(context, sizeof(*context)); + } + MEMSET_BZERO(digest, SHA512_DIGEST_LENGTH); + return buffer; +} + +char* SHA512_Data(const sha2_byte* data, size_t len, char digest[SHA512_DIGEST_STRING_LENGTH]) { + SHA512_CTX context; + + SHA512_Init(&context); + SHA512_Update(&context, data, len); + return SHA512_End(&context, digest); +} + + +/*** SHA-384: *********************************************************/ +void SHA384_Init(SHA384_CTX* context) { + if (context == (SHA384_CTX*)0) { + return; + } + MEMCPY_BCOPY(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH); + MEMSET_BZERO(context->buffer, SHA384_BLOCK_LENGTH); + context->bitcount[0] = context->bitcount[1] = 0; +} + +void SHA384_Update(SHA384_CTX* context, const sha2_byte* data, size_t len) { + SHA512_Update((SHA512_CTX*)context, data, len); +} + +void SHA384_Final(sha2_byte digest[], SHA384_CTX* context) { + sha2_word64 *d = (sha2_word64*)digest; + + /* Sanity check: */ + assert(context != (SHA384_CTX*)0); + + /* If no digest buffer is passed, we don't bother doing this: */ + if (digest != (sha2_byte*)0) { + SHA512_Last((SHA512_CTX*)context); + + /* Save the hash data for output: */ +#if BYTE_ORDER == LITTLE_ENDIAN + { + /* Convert TO host byte order */ + int j; + for (j = 0; j < 6; j++) { + REVERSE64(context->state[j],context->state[j]); + *d++ = context->state[j]; + } + } +#else + MEMCPY_BCOPY(d, context->state, SHA384_DIGEST_LENGTH); +#endif + } + + /* Zero out state data */ + MEMSET_BZERO(context, sizeof(*context)); +} + +char *SHA384_End(SHA384_CTX* context, char buffer[]) { + sha2_byte digest[SHA384_DIGEST_LENGTH], *d = digest; + int i; + + /* Sanity check: */ + assert(context != (SHA384_CTX*)0); + + if (buffer != (char*)0) { + SHA384_Final(digest, context); + + for (i = 0; i < SHA384_DIGEST_LENGTH; i++) { + *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; + *buffer++ = sha2_hex_digits[*d & 0x0f]; + d++; + } + *buffer = (char)0; + } else { + MEMSET_BZERO(context, sizeof(*context)); + } + MEMSET_BZERO(digest, SHA384_DIGEST_LENGTH); + return buffer; +} + +char* SHA384_Data(const sha2_byte* data, size_t len, char digest[SHA384_DIGEST_STRING_LENGTH]) { + SHA384_CTX context; + + SHA384_Init(&context); + SHA384_Update(&context, data, len); + return SHA384_End(&context, digest); +} + diff --git a/apt-pkg/contrib/sha2_internal.h b/apt-pkg/contrib/sha2_internal.h new file mode 100644 index 0000000..78d1d36 --- /dev/null +++ b/apt-pkg/contrib/sha2_internal.h @@ -0,0 +1,188 @@ +/* + * FILE: sha2.h + * AUTHOR: Aaron D. Gifford - http://www.aarongifford.com/ + * + * Copyright (c) 2000-2001, Aaron D. Gifford + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef __SHA2_H__ +#define __SHA2_H__ + +/* + * Import u_intXX_t size_t type definitions from system headers. You + * may need to change this, or define these things yourself in this + * file. + */ +#include <sys/types.h> + +#ifdef SHA2_USE_INTTYPES_H + +#include <inttypes.h> +#include <stddef.h> + +#endif /* SHA2_USE_INTTYPES_H */ + + +/*** SHA-256/384/512 Various Length Definitions ***********************/ +#define SHA256_BLOCK_LENGTH 64 +#define SHA256_DIGEST_LENGTH 32 +#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1) +#define SHA384_BLOCK_LENGTH 128 +#define SHA384_DIGEST_LENGTH 48 +#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1) +#define SHA512_BLOCK_LENGTH 128 +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1) + + +/*** SHA-256/384/512 Context Structures *******************************/ +/* NOTE: If your architecture does not define either u_intXX_t types or + * uintXX_t (from inttypes.h), you may need to define things by hand + * for your system: + */ +#if 0 +typedef unsigned char u_int8_t; /* 1-byte (8-bits) */ +typedef unsigned int u_int32_t; /* 4-bytes (32-bits) */ +typedef unsigned long long u_int64_t; /* 8-bytes (64-bits) */ +#endif +/* + * Most BSD systems already define u_intXX_t types, as does Linux. + * Some systems, however, like Compaq's Tru64 Unix instead can use + * uintXX_t types defined by very recent ANSI C standards and included + * in the file: + * + * #include <inttypes.h> + * + * If you choose to use <inttypes.h> then please define: + * + * #define SHA2_USE_INTTYPES_H + * + * Or on the command line during compile: + * + * cc -DSHA2_USE_INTTYPES_H ... + */ +#ifdef SHA2_USE_INTTYPES_H + +typedef struct _SHA256_CTX { + uint32_t state[8]; + uint64_t bitcount; + uint8_t buffer[SHA256_BLOCK_LENGTH]; +} SHA256_CTX; +typedef struct _SHA512_CTX { + uint64_t state[8]; + uint64_t bitcount[2]; + uint8_t buffer[SHA512_BLOCK_LENGTH]; +} SHA512_CTX; + +#else /* SHA2_USE_INTTYPES_H */ + +typedef struct _SHA256_CTX { + u_int32_t state[8]; + u_int64_t bitcount; + u_int8_t buffer[SHA256_BLOCK_LENGTH]; +} SHA256_CTX; +typedef struct _SHA512_CTX { + u_int64_t state[8]; + u_int64_t bitcount[2]; + u_int8_t buffer[SHA512_BLOCK_LENGTH]; +} SHA512_CTX; + +#endif /* SHA2_USE_INTTYPES_H */ + +typedef SHA512_CTX SHA384_CTX; + + +/*** SHA-256/384/512 Function Prototypes ******************************/ +#ifndef NOPROTO +#ifdef SHA2_USE_INTTYPES_H + +void SHA256_Init(SHA256_CTX *); +void SHA256_Update(SHA256_CTX*, const uint8_t*, size_t); +void SHA256_Final(uint8_t[SHA256_DIGEST_LENGTH], SHA256_CTX*); +char* SHA256_End(SHA256_CTX*, char[SHA256_DIGEST_STRING_LENGTH]); +char* SHA256_Data(const uint8_t*, size_t, char[SHA256_DIGEST_STRING_LENGTH]); + +void SHA384_Init(SHA384_CTX*); +void SHA384_Update(SHA384_CTX*, const uint8_t*, size_t); +void SHA384_Final(uint8_t[SHA384_DIGEST_LENGTH], SHA384_CTX*); +char* SHA384_End(SHA384_CTX*, char[SHA384_DIGEST_STRING_LENGTH]); +char* SHA384_Data(const uint8_t*, size_t, char[SHA384_DIGEST_STRING_LENGTH]); + +void SHA512_Init(SHA512_CTX*); +void SHA512_Update(SHA512_CTX*, const uint8_t*, size_t); +void SHA512_Final(uint8_t[SHA512_DIGEST_LENGTH], SHA512_CTX*); +char* SHA512_End(SHA512_CTX*, char[SHA512_DIGEST_STRING_LENGTH]); +char* SHA512_Data(const uint8_t*, size_t, char[SHA512_DIGEST_STRING_LENGTH]); + +#else /* SHA2_USE_INTTYPES_H */ + +void SHA256_Init(SHA256_CTX *); +void SHA256_Update(SHA256_CTX*, const u_int8_t*, size_t); +void SHA256_Final(u_int8_t[SHA256_DIGEST_LENGTH], SHA256_CTX*); +char* SHA256_End(SHA256_CTX*, char[SHA256_DIGEST_STRING_LENGTH]); +char* SHA256_Data(const u_int8_t*, size_t, char[SHA256_DIGEST_STRING_LENGTH]); + +void SHA384_Init(SHA384_CTX*); +void SHA384_Update(SHA384_CTX*, const u_int8_t*, size_t); +void SHA384_Final(u_int8_t[SHA384_DIGEST_LENGTH], SHA384_CTX*); +char* SHA384_End(SHA384_CTX*, char[SHA384_DIGEST_STRING_LENGTH]); +char* SHA384_Data(const u_int8_t*, size_t, char[SHA384_DIGEST_STRING_LENGTH]); + +void SHA512_Init(SHA512_CTX*); +void SHA512_Update(SHA512_CTX*, const u_int8_t*, size_t); +void SHA512_Final(u_int8_t[SHA512_DIGEST_LENGTH], SHA512_CTX*); +char* SHA512_End(SHA512_CTX*, char[SHA512_DIGEST_STRING_LENGTH]); +char* SHA512_Data(const u_int8_t*, size_t, char[SHA512_DIGEST_STRING_LENGTH]); + +#endif /* SHA2_USE_INTTYPES_H */ + +#else /* NOPROTO */ + +void SHA256_Init(); +void SHA256_Update(); +void SHA256_Final(); +char* SHA256_End(); +char* SHA256_Data(); + +void SHA384_Init(); +void SHA384_Update(); +void SHA384_Final(); +char* SHA384_End(); +char* SHA384_Data(); + +void SHA512_Init(); +void SHA512_Update(); +void SHA512_Final(); +char* SHA512_End(); +char* SHA512_Data(); + +#endif /* NOPROTO */ + +#endif /* __SHA2_H__ */ + diff --git a/apt-pkg/contrib/sptr.h b/apt-pkg/contrib/sptr.h new file mode 100644 index 0000000..77806d9 --- /dev/null +++ b/apt-pkg/contrib/sptr.h @@ -0,0 +1,74 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Trivial non-ref counted 'smart pointer' + + This is really only good to eliminate + { + delete Foo; + return; + } + + Blocks from functions. + + I think G++ has become good enough that doing this won't have much + code size implications. + + ##################################################################### */ + /*}}}*/ +#ifndef SMART_POINTER_H +#define SMART_POINTER_H +#include <apt-pkg/macros.h> + +template <class T> +class APT_DEPRECATED_MSG("use std::unique_ptr instead") SPtr +{ + public: + T *Ptr; + + inline T *operator ->() {return Ptr;}; + inline T &operator *() {return *Ptr;}; + inline operator T *() {return Ptr;}; + inline operator void *() {return Ptr;}; + inline T *UnGuard() {T *Tmp = Ptr; Ptr = 0; return Tmp;}; + inline void operator =(T *N) {Ptr = N;}; + inline bool operator ==(T *lhs) const {return Ptr == lhs;}; + inline bool operator !=(T *lhs) const {return Ptr != lhs;}; + inline T*Get() {return Ptr;}; + + inline SPtr(T *Ptr) : Ptr(Ptr) {}; + inline SPtr() : Ptr(0) {}; + inline ~SPtr() {delete Ptr;}; +}; + +template <class T> +class APT_DEPRECATED_MSG("use std::unique_ptr instead") SPtrArray +{ + public: + T *Ptr; + + //inline T &operator *() {return *Ptr;}; + inline operator T *() {return Ptr;}; + inline operator void *() {return Ptr;}; + inline T *UnGuard() {T *Tmp = Ptr; Ptr = 0; return Tmp;}; + //inline T &operator [](signed long I) {return Ptr[I];}; + inline void operator =(T *N) {Ptr = N;}; + inline bool operator ==(T *lhs) const {return Ptr == lhs;}; + inline bool operator !=(T *lhs) const {return Ptr != lhs;}; + inline T *Get() {return Ptr;}; + + inline SPtrArray(T *Ptr) : Ptr(Ptr) {}; + inline SPtrArray() : Ptr(0) {}; +#if __GNUC__ >= 4 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunsafe-loop-optimizations" + // gcc warns about this, but we can do nothing here… +#endif + inline ~SPtrArray() {delete [] Ptr;}; +#if __GNUC__ >= 4 + #pragma GCC diagnostic pop +#endif +}; + +#endif diff --git a/apt-pkg/contrib/srvrec.cc b/apt-pkg/contrib/srvrec.cc new file mode 100644 index 0000000..a97d9c6 --- /dev/null +++ b/apt-pkg/contrib/srvrec.cc @@ -0,0 +1,204 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + SRV record support + + ##################################################################### */ + /*}}}*/ +#include <config.h> + +#include <netdb.h> + +#include <arpa/nameser.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <resolv.h> +#include <time.h> + +#include <algorithm> +#include <tuple> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/strutl.h> + +#include "srvrec.h" + +bool SrvRec::operator==(SrvRec const &other) const +{ + return (std::tie(target, priority, weight, port) == + std::tie(other.target, other.priority, other.weight, other.port)); +} + +bool GetSrvRecords(std::string host, int port, std::vector<SrvRec> &Result) +{ + // try SRV only for hostnames, not for IP addresses + { + struct in_addr addr4; + struct in6_addr addr6; + if (inet_pton(AF_INET, host.c_str(), &addr4) == 1 || + inet_pton(AF_INET6, host.c_str(), &addr6) == 1) + return true; + } + + std::string target; + int res; + struct servent s_ent_buf; + struct servent *s_ent = nullptr; + std::vector<char> buf(1024); + + res = getservbyport_r(htons(port), "tcp", &s_ent_buf, buf.data(), buf.size(), &s_ent); + if (res != 0 || s_ent == nullptr) + return false; + + strprintf(target, "_%s._tcp.%s", s_ent->s_name, host.c_str()); + return GetSrvRecords(target, Result); +} + +bool GetSrvRecords(std::string name, std::vector<SrvRec> &Result) +{ + unsigned char answer[PACKETSZ]; + int answer_len, compressed_name_len; + int answer_count; + + if (res_init() != 0) + return _error->Errno("res_init", "Failed to init resolver"); + + answer_len = res_query(name.c_str(), C_IN, T_SRV, answer, sizeof(answer)); + if (answer_len == -1) + return false; + if (answer_len < (int)sizeof(HEADER)) + return _error->Warning("Not enough data from res_query (%i)", answer_len); + + // check the header + HEADER *header = (HEADER*)answer; + if (header->rcode != NOERROR) + return _error->Warning("res_query returned rcode %i", header->rcode); + answer_count = ntohs(header->ancount); + if (answer_count <= 0) + return _error->Warning("res_query returned no answers (%i) ", answer_count); + + // skip the header + compressed_name_len = dn_skipname(answer+sizeof(HEADER), answer+answer_len); + if(compressed_name_len < 0) + return _error->Warning("dn_skipname failed %i", compressed_name_len); + + // pt points to the first answer record, go over all of them now + unsigned char *pt = answer+sizeof(HEADER)+compressed_name_len+QFIXEDSZ; + while ((int)Result.size() < answer_count && pt < answer+answer_len) + { + u_int16_t type, klass, priority, weight, port, dlen; + char buf[MAXDNAME]; + + compressed_name_len = dn_skipname(pt, answer+answer_len); + if (compressed_name_len < 0) + return _error->Warning("dn_skipname failed (2): %i", + compressed_name_len); + pt += compressed_name_len; + if (((answer+answer_len) - pt) < 16) + return _error->Warning("packet too short"); + + // extract the data out of the result buffer + #define extract_u16(target, p) target = *p++ << 8; target |= *p++; + + extract_u16(type, pt); + if(type != T_SRV) + return _error->Warning("Unexpected type excepted %x != %x", + T_SRV, type); + extract_u16(klass, pt); + if(klass != C_IN) + return _error->Warning("Unexpected class excepted %x != %x", + C_IN, klass); + pt += 4; // ttl + extract_u16(dlen, pt); + extract_u16(priority, pt); + extract_u16(weight, pt); + extract_u16(port, pt); + + #undef extract_u16 + + compressed_name_len = dn_expand(answer, answer+answer_len, pt, buf, sizeof(buf)); + if(compressed_name_len < 0) + return _error->Warning("dn_expand failed %i", compressed_name_len); + pt += compressed_name_len; + + // add it to our class + Result.emplace_back(buf, priority, weight, port); + } + + // implement load balancing as specified in RFC-2782 + + // sort them by priority + std::stable_sort(Result.begin(), Result.end()); + + for(std::vector<SrvRec>::iterator I = Result.begin(); + I != Result.end(); ++I) + { + if (_config->FindB("Debug::Acquire::SrvRecs", false) == true) + { + std::cerr << "SrvRecs: got " << I->target + << " prio: " << I->priority + << " weight: " << I->weight + << std::endl; + } + } + + return true; +} + +SrvRec PopFromSrvRecs(std::vector<SrvRec> &Recs) +{ + // FIXME: instead of the simplistic shuffle below use the algorithm + // described in rfc2782 (with weights) + // and figure out how the weights need to be adjusted if + // a host refuses connections + +#if 0 // all code below is only needed for the weight adjusted selection + // assign random number ranges + int prev_weight = 0; + int prev_priority = 0; + for(std::vector<SrvRec>::iterator I = Result.begin(); + I != Result.end(); ++I) + { + if(prev_priority != I->priority) + prev_weight = 0; + I->random_number_range_start = prev_weight; + I->random_number_range_end = prev_weight + I->weight; + prev_weight = I->random_number_range_end; + prev_priority = I->priority; + + if (_config->FindB("Debug::Acquire::SrvRecs", false) == true) + std::cerr << "SrvRecs: got " << I->target + << " prio: " << I->priority + << " weight: " << I->weight + << std::endl; + } + + // go over the code in reverse order and note the max random range + int max = 0; + prev_priority = 0; + for(std::vector<SrvRec>::iterator I = Result.end(); + I != Result.begin(); --I) + { + if(prev_priority != I->priority) + max = I->random_number_range_end; + I->random_number_range_max = max; + } +#endif + + // shuffle in a very simplistic way for now (equal weights) + std::vector<SrvRec>::iterator I = Recs.begin(); + std::vector<SrvRec>::iterator const J = std::find_if(Recs.begin(), Recs.end(), + [&I](SrvRec const &J) { return I->priority != J.priority; }); + + // clock seems random enough. + I += std::max(static_cast<clock_t>(0), clock()) % std::distance(I, J); + SrvRec const selected = std::move(*I); + Recs.erase(I); + + if (_config->FindB("Debug::Acquire::SrvRecs", false) == true) + std::cerr << "PopFromSrvRecs: selecting " << selected.target << std::endl; + + return selected; +} diff --git a/apt-pkg/contrib/srvrec.h b/apt-pkg/contrib/srvrec.h new file mode 100644 index 0000000..e22b7a1 --- /dev/null +++ b/apt-pkg/contrib/srvrec.h @@ -0,0 +1,54 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + SRV record support + + ##################################################################### */ + /*}}}*/ +#ifndef SRVREC_H +#define SRVREC_H + +#include <string> +#include <vector> +#include <arpa/nameser.h> + +class SrvRec +{ + public: + std::string target; + u_int16_t priority; + u_int16_t weight; + u_int16_t port; + + // each server is assigned a interval [start, end] in the space of [0, max] + int random_number_range_start; + int random_number_range_end; + int random_number_range_max; + + bool operator<(SrvRec const &other) const { + return this->priority < other.priority; + } + bool operator==(SrvRec const &other) const; + + SrvRec(std::string const Target, u_int16_t const Priority, + u_int16_t const Weight, u_int16_t const Port) : + target(Target), priority(Priority), weight(Weight), port(Port), + random_number_range_start(0), random_number_range_end(0), + random_number_range_max(0) {} +}; + +/** \brief Get SRV records from host/port (builds the query string internally) + */ +bool GetSrvRecords(std::string name, std::vector<SrvRec> &Result); + +/** \brief Get SRV records for query string like: _http._tcp.example.com + */ +bool GetSrvRecords(std::string host, int port, std::vector<SrvRec> &Result); + +/** \brief Pop a single SRV record from the vector of SrvRec taking + * priority and weight into account + */ +SrvRec PopFromSrvRecs(std::vector<SrvRec> &Recs); + +#endif diff --git a/apt-pkg/contrib/string_view.h b/apt-pkg/contrib/string_view.h new file mode 100644 index 0000000..536744e --- /dev/null +++ b/apt-pkg/contrib/string_view.h @@ -0,0 +1,132 @@ +/* + * Basic implementation of string_view + * + * (C) 2015 Julian Andres Klode <jak@debian.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#if !defined(APT_STRINGVIEW_H) && defined(APT_PKG_EXPOSE_STRING_VIEW) +#define APT_STRINGVIEW_H +#include <apt-pkg/macros.h> +#include <string> +#include <string.h> + +namespace APT { + +/** + * \brief Simple subset of std::string_view from C++17 + * + * This is an internal implementation of the subset of std::string_view + * used by APT. It is not meant to be used in programs, only inside the + * library for performance critical paths. + */ +class APT_HIDDEN StringView { + const char *data_; + size_t size_; + +public: + static constexpr size_t npos = static_cast<size_t>(-1); + static_assert(APT::StringView::npos == std::string::npos, "npos values are different"); + + /* Constructors */ + constexpr StringView() : data_(""), size_(0) {} + constexpr StringView(const char *data, size_t size) : data_(data), size_(size) {} + + StringView(const char *data) : data_(data), size_(strlen(data)) {} + StringView(std::string const & str): data_(str.data()), size_(str.size()) {} + + + /* Viewers */ + constexpr StringView substr(size_t pos, size_t n = npos) const { + return StringView(data_ + pos, n > (size_ - pos) ? (size_ - pos) : n); + } + + size_t find(int c, size_t pos) const { + if (pos == 0) + return find(c); + size_t const found = substr(pos).find(c); + if (found == npos) + return npos; + return pos + found; + } + size_t find(int c) const { + const char *found = static_cast<const char*>(memchr(data_, c, size_)); + + if (found == NULL) + return npos; + + return found - data_; + } + + size_t rfind(int c, size_t pos) const { + if (pos == npos) + return rfind(c); + return APT::StringView(data_, pos).rfind(c); + } + size_t rfind(int c) const { + const char *found = static_cast<const char*>(memrchr(data_, c, size_)); + + if (found == NULL) + return npos; + + return found - data_; + } + + /* Conversions */ + std::string to_string() const { + return std::string(data_, size_); + } + + /* Comparisons */ + int compare(size_t pos, size_t n, StringView other) const { + return substr(pos, n).compare(other); + } + + int compare(StringView other) const { + int res; + + res = memcmp(data_, other.data_, std::min(size_, other.size_)); + if (res != 0) + return res; + if (size_ == other.size_) + return res; + + return (size_ > other.size_) ? 1 : -1; + } + + /* Optimization: If size not equal, string cannot be equal */ + bool operator ==(StringView other) const { return size_ == other.size_ && compare(other) == 0; } + bool operator !=(StringView other) const { return !(*this == other); } + + /* Accessors */ + constexpr bool empty() const { return size_ == 0; } + constexpr const char* data() const { return data_; } + constexpr const char* begin() const { return data_; } + constexpr const char* end() const { return data_ + size_; } + constexpr char operator [](size_t i) const { return data_[i]; } + constexpr size_t size() const { return size_; } + constexpr size_t length() const { return size_; } +}; + +/** + * \brief Faster comparison for string views (compare size before data) + * + * Still stable, but faster than the normal ordering. */ +static inline int StringViewCompareFast(StringView a, StringView b) { + if (a.size() != b.size()) + return a.size() - b.size(); + + return memcmp(a.data(), b.data(), a.size()); +} + + +} + +inline bool operator ==(const char *other, APT::StringView that); +inline bool operator ==(const char *other, APT::StringView that) { return that.operator==(other); } + +#endif diff --git a/apt-pkg/contrib/strutl.cc b/apt-pkg/contrib/strutl.cc new file mode 100644 index 0000000..50344d1 --- /dev/null +++ b/apt-pkg/contrib/strutl.cc @@ -0,0 +1,1840 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + String Util - Some useful string functions. + + These have been collected from here and there to do all sorts of useful + things to strings. They are useful in file parsers, URI handlers and + especially in APT methods. + + This source is placed in the Public Domain, do with it what you will + It was originally written by Jason Gunthorpe <jgg@gpu.srv.ualberta.ca> + + ##################################################################### */ + /*}}}*/ +// Includes /*{{{*/ +#include <config.h> + +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/strutl.h> + +#include <algorithm> +#include <array> +#include <iomanip> +#include <locale> +#include <sstream> +#include <sstream> +#include <string> +#include <vector> + +#include <ctype.h> +#include <errno.h> +#include <iconv.h> +#include <regex.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ +using namespace std; + +// Strip - Remove white space from the front and back of a string /*{{{*/ +// --------------------------------------------------------------------- +namespace APT { + namespace String { +std::string Strip(const std::string &str) +{ + // ensure we have at least one character + if (str.empty() == true) + return str; + + char const * const s = str.c_str(); + size_t start = 0; + for (; isspace(s[start]) != 0; ++start) + ; // find the first not-space + + // string contains only whitespaces + if (s[start] == '\0') + return ""; + + size_t end = str.length() - 1; + for (; isspace(s[end]) != 0; --end) + ; // find the last not-space + + return str.substr(start, end - start + 1); +} + +bool Endswith(const std::string &s, const std::string &end) +{ + if (end.size() > s.size()) + return false; + return (s.compare(s.size() - end.size(), end.size(), end) == 0); +} + +bool Startswith(const std::string &s, const std::string &start) +{ + if (start.size() > s.size()) + return false; + return (s.compare(0, start.size(), start) == 0); +} + +std::string Join(std::vector<std::string> list, const std::string &sep) +{ + std::ostringstream oss; + for (auto it = list.begin(); it != list.end(); it++) + { + if (it != list.begin()) oss << sep; + oss << *it; + } + return oss.str(); +} + +} +} + /*}}}*/ +// UTF8ToCodeset - Convert some UTF-8 string for some codeset /*{{{*/ +// --------------------------------------------------------------------- +/* This is handy to use before display some information for enduser */ +bool UTF8ToCodeset(const char *codeset, const string &orig, string *dest) +{ + iconv_t cd; + const char *inbuf; + char *inptr, *outbuf; + size_t insize, bufsize; + dest->clear(); + + cd = iconv_open(codeset, "UTF-8"); + if (cd == (iconv_t)(-1)) { + // Something went wrong + if (errno == EINVAL) + _error->Error("conversion from 'UTF-8' to '%s' not available", + codeset); + else + perror("iconv_open"); + + return false; + } + + insize = bufsize = orig.size(); + inbuf = orig.data(); + inptr = (char *)inbuf; + outbuf = new char[bufsize]; + size_t lastError = -1; + + while (insize != 0) + { + char *outptr = outbuf; + size_t outsize = bufsize; + size_t const err = iconv(cd, &inptr, &insize, &outptr, &outsize); + dest->append(outbuf, outptr - outbuf); + if (err == (size_t)(-1)) + { + switch (errno) + { + case EILSEQ: + insize--; + inptr++; + // replace a series of unknown multibytes with a single "?" + if (lastError != insize) { + lastError = insize - 1; + dest->append("?"); + } + break; + case EINVAL: + insize = 0; + break; + case E2BIG: + if (outptr == outbuf) + { + bufsize *= 2; + delete[] outbuf; + outbuf = new char[bufsize]; + } + break; + } + } + } + + delete[] outbuf; + + iconv_close(cd); + + return true; +} + /*}}}*/ +// strstrip - Remove white space from the front and back of a string /*{{{*/ +// --------------------------------------------------------------------- +/* This is handy to use when parsing a file. It also removes \n's left + over from fgets and company */ +char *_strstrip(char *String) +{ + for (;*String != 0 && (*String == ' ' || *String == '\t'); String++); + + if (*String == 0) + return String; + return _strrstrip(String); +} + /*}}}*/ +// strrstrip - Remove white space from the back of a string /*{{{*/ +// --------------------------------------------------------------------- +char *_strrstrip(char *String) +{ + char *End = String + strlen(String) - 1; + for (;End != String - 1 && (*End == ' ' || *End == '\t' || *End == '\n' || + *End == '\r'); End--); + End++; + *End = 0; + return String; +} + /*}}}*/ +// strtabexpand - Converts tabs into 8 spaces /*{{{*/ +// --------------------------------------------------------------------- +/* */ +char *_strtabexpand(char *String,size_t Len) +{ + for (char *I = String; I != I + Len && *I != 0; I++) + { + if (*I != '\t') + continue; + if (I + 8 > String + Len) + { + *I = 0; + return String; + } + + /* Assume the start of the string is 0 and find the next 8 char + division */ + int Len; + if (String == I) + Len = 1; + else + Len = 8 - ((String - I) % 8); + Len -= 2; + if (Len <= 0) + { + *I = ' '; + continue; + } + + memmove(I + Len,I + 1,strlen(I) + 1); + for (char *J = I; J + Len != I; *I = ' ', I++); + } + return String; +} + /*}}}*/ +// ParseQuoteWord - Parse a single word out of a string /*{{{*/ +// --------------------------------------------------------------------- +/* This grabs a single word, converts any % escaped characters to their + proper values and advances the pointer. Double quotes are understood + and striped out as well. This is for URI/URL parsing. It also can + understand [] brackets.*/ +bool ParseQuoteWord(const char *&String,string &Res) +{ + // Skip leading whitespace + const char *C = String; + for (;*C != 0 && *C == ' '; C++); + if (*C == 0) + return false; + + // Jump to the next word + for (;*C != 0 && isspace(*C) == 0; C++) + { + if (*C == '"') + { + C = strchr(C + 1, '"'); + if (C == NULL) + return false; + } + if (*C == '[') + { + C = strchr(C + 1, ']'); + if (C == NULL) + return false; + } + } + + // Now de-quote characters + char Buffer[1024]; + char Tmp[3]; + const char *Start = String; + char *I; + for (I = Buffer; I < Buffer + sizeof(Buffer) && Start != C; I++) + { + if (*Start == '%' && Start + 2 < C && + isxdigit(Start[1]) && isxdigit(Start[2])) + { + Tmp[0] = Start[1]; + Tmp[1] = Start[2]; + Tmp[2] = 0; + *I = (char)strtol(Tmp,0,16); + Start += 3; + continue; + } + if (*Start != '"') + *I = *Start; + else + I--; + Start++; + } + *I = 0; + Res = Buffer; + + // Skip ending white space + for (;*C != 0 && isspace(*C) != 0; C++); + String = C; + return true; +} + /*}}}*/ +// ParseCWord - Parses a string like a C "" expression /*{{{*/ +// --------------------------------------------------------------------- +/* This expects a series of space separated strings enclosed in ""'s. + It concatenates the ""'s into a single string. */ +bool ParseCWord(const char *&String,string &Res) +{ + // Skip leading whitespace + const char *C = String; + for (;*C != 0 && *C == ' '; C++); + if (*C == 0) + return false; + + char Buffer[1024]; + char *Buf = Buffer; + if (strlen(String) >= sizeof(Buffer)) + return false; + + for (; *C != 0; C++) + { + if (*C == '"') + { + for (C++; *C != 0 && *C != '"'; C++) + *Buf++ = *C; + + if (*C == 0) + return false; + + continue; + } + + if (C != String && isspace(*C) != 0 && isspace(C[-1]) != 0) + continue; + if (isspace(*C) == 0) + return false; + *Buf++ = ' '; + } + *Buf = 0; + Res = Buffer; + String = C; + return true; +} + /*}}}*/ +// QuoteString - Convert a string into quoted from /*{{{*/ +// --------------------------------------------------------------------- +/* */ +string QuoteString(const string &Str, const char *Bad) +{ + std::stringstream Res; + for (string::const_iterator I = Str.begin(); I != Str.end(); ++I) + { + if (strchr(Bad,*I) != 0 || isprint(*I) == 0 || + *I == 0x25 || // percent '%' char + *I <= 0x20 || *I >= 0x7F) // control chars + { + ioprintf(Res, "%%%02hhx", *I); + } + else + Res << *I; + } + return Res.str(); +} + /*}}}*/ +// DeQuoteString - Convert a string from quoted from /*{{{*/ +// --------------------------------------------------------------------- +/* This undoes QuoteString */ +string DeQuoteString(const string &Str) +{ + return DeQuoteString(Str.begin(),Str.end()); +} +string DeQuoteString(string::const_iterator const &begin, + string::const_iterator const &end) +{ + string Res; + for (string::const_iterator I = begin; I != end; ++I) + { + if (*I == '%' && I + 2 < end && + isxdigit(I[1]) && isxdigit(I[2])) + { + char Tmp[3]; + Tmp[0] = I[1]; + Tmp[1] = I[2]; + Tmp[2] = 0; + Res += (char)strtol(Tmp,0,16); + I += 2; + continue; + } + else + Res += *I; + } + return Res; +} + + /*}}}*/ +// SizeToStr - Convert a long into a human readable size /*{{{*/ +// --------------------------------------------------------------------- +/* A max of 4 digits are shown before conversion to the next highest unit. + The max length of the string will be 5 chars unless the size is > 10 + YottaBytes (E24) */ +string SizeToStr(double Size) +{ + double ASize; + if (Size >= 0) + ASize = Size; + else + ASize = -1*Size; + + /* bytes, KiloBytes, MegaBytes, GigaBytes, TeraBytes, PetaBytes, + ExaBytes, ZettaBytes, YottaBytes */ + char Ext[] = {'\0','k','M','G','T','P','E','Z','Y'}; + int I = 0; + while (I <= 8) + { + if (ASize < 100 && I != 0) + { + std::string S; + strprintf(S, "%'.1f %c", ASize, Ext[I]); + return S; + } + + if (ASize < 10000) + { + std::string S; + strprintf(S, "%'.0f %c", ASize, Ext[I]); + return S; + } + ASize /= 1000.0; + I++; + } + return ""; +} + /*}}}*/ +// TimeToStr - Convert the time into a string /*{{{*/ +// --------------------------------------------------------------------- +/* Converts a number of seconds to a hms format */ +string TimeToStr(unsigned long Sec) +{ + std::string S; + if (Sec > 60*60*24) + { + //TRANSLATOR: d means days, h means hours, min means minutes, s means seconds + strprintf(S,_("%lid %lih %limin %lis"),Sec/60/60/24,(Sec/60/60) % 24,(Sec/60) % 60,Sec % 60); + } + else if (Sec > 60*60) + { + //TRANSLATOR: h means hours, min means minutes, s means seconds + strprintf(S,_("%lih %limin %lis"),Sec/60/60,(Sec/60) % 60,Sec % 60); + } + else if (Sec > 60) + { + //TRANSLATOR: min means minutes, s means seconds + strprintf(S,_("%limin %lis"),Sec/60,Sec % 60); + } + else + { + //TRANSLATOR: s means seconds + strprintf(S,_("%lis"),Sec); + } + return S; +} + /*}}}*/ +// SubstVar - Substitute a string for another string /*{{{*/ +// --------------------------------------------------------------------- +/* This replaces all occurrences of Subst with Contents in Str. */ +string SubstVar(const string &Str,const string &Subst,const string &Contents) +{ + if (Subst.empty() == true) + return Str; + + string::size_type Pos = 0; + string::size_type OldPos = 0; + string Temp; + + while (OldPos < Str.length() && + (Pos = Str.find(Subst,OldPos)) != string::npos) + { + if (OldPos != Pos) + Temp.append(Str, OldPos, Pos - OldPos); + if (Contents.empty() == false) + Temp.append(Contents); + OldPos = Pos + Subst.length(); + } + + if (OldPos == 0) + return Str; + + if (OldPos >= Str.length()) + return Temp; + + Temp.append(Str, OldPos, string::npos); + return Temp; +} +string SubstVar(string Str,const struct SubstVar *Vars) +{ + for (; Vars->Subst != 0; Vars++) + Str = SubstVar(Str,Vars->Subst,*Vars->Contents); + return Str; +} + /*}}}*/ +// OutputInDepth - return a string with separator multiplied with depth /*{{{*/ +// --------------------------------------------------------------------- +/* Returns a string with the supplied separator depth + 1 times in it */ +std::string OutputInDepth(const unsigned long Depth, const char* Separator) +{ + std::string output = ""; + for(unsigned long d=Depth+1; d > 0; d--) + output.append(Separator); + return output; +} + /*}}}*/ +// URItoFileName - Convert the uri into a unique file name /*{{{*/ +// --------------------------------------------------------------------- +/* This converts a URI into a safe filename. It quotes all unsafe characters + and converts / to _ and removes the scheme identifier. The resulting + file name should be unique and never occur again for a different file */ +string URItoFileName(const string &URI) +{ + // Nuke 'sensitive' items + ::URI U(URI); + U.User.clear(); + U.Password.clear(); + U.Access.clear(); + + // "\x00-\x20{}|\\\\^\\[\\]<>\"\x7F-\xFF"; + string NewURI = QuoteString(U,"\\|{}[]<>\"^~_=!@#$%^&*"); + replace(NewURI.begin(),NewURI.end(),'/','_'); + return NewURI; +} + /*}}}*/ +// Base64Encode - Base64 Encoding routine for short strings /*{{{*/ +// --------------------------------------------------------------------- +/* This routine performs a base64 transformation on a string. It was ripped + from wget and then patched and bug fixed. + + This spec can be found in rfc2045 */ +string Base64Encode(const string &S) +{ + // Conversion table. + static char tbl[64] = {'A','B','C','D','E','F','G','H', + 'I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X', + 'Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n', + 'o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3', + '4','5','6','7','8','9','+','/'}; + + // Pre-allocate some space + string Final; + Final.reserve((4*S.length() + 2)/3 + 2); + + /* Transform the 3x8 bits to 4x6 bits, as required by + base64. */ + for (string::const_iterator I = S.begin(); I < S.end(); I += 3) + { + char Bits[3] = {0,0,0}; + Bits[0] = I[0]; + if (I + 1 < S.end()) + Bits[1] = I[1]; + if (I + 2 < S.end()) + Bits[2] = I[2]; + + Final += tbl[Bits[0] >> 2]; + Final += tbl[((Bits[0] & 3) << 4) + (Bits[1] >> 4)]; + + if (I + 1 >= S.end()) + break; + + Final += tbl[((Bits[1] & 0xf) << 2) + (Bits[2] >> 6)]; + + if (I + 2 >= S.end()) + break; + + Final += tbl[Bits[2] & 0x3f]; + } + + /* Apply the padding elements, this tells how many bytes the remote + end should discard */ + if (S.length() % 3 == 2) + Final += '='; + if (S.length() % 3 == 1) + Final += "=="; + + return Final; +} + /*}}}*/ +// stringcmp - Arbitrary string compare /*{{{*/ +// --------------------------------------------------------------------- +/* This safely compares two non-null terminated strings of arbitrary + length */ +int stringcmp(const char *A,const char *AEnd,const char *B,const char *BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (*A != *B) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (*A < *B) + return -1; + return 1; +} + +#if __GNUC__ >= 3 +int stringcmp(string::const_iterator A,string::const_iterator AEnd, + const char *B,const char *BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (*A != *B) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (*A < *B) + return -1; + return 1; +} +int stringcmp(string::const_iterator A,string::const_iterator AEnd, + string::const_iterator B,string::const_iterator BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (*A != *B) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (*A < *B) + return -1; + return 1; +} +#endif + /*}}}*/ +// stringcasecmp - Arbitrary case insensitive string compare /*{{{*/ +// --------------------------------------------------------------------- +/* */ +int stringcasecmp(const char *A,const char *AEnd,const char *B,const char *BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (tolower_ascii(*A) != tolower_ascii(*B)) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (tolower_ascii(*A) < tolower_ascii(*B)) + return -1; + return 1; +} +#if __GNUC__ >= 3 +int stringcasecmp(string::const_iterator A,string::const_iterator AEnd, + const char *B,const char *BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (tolower_ascii(*A) != tolower_ascii(*B)) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (tolower_ascii(*A) < tolower_ascii(*B)) + return -1; + return 1; +} +int stringcasecmp(string::const_iterator A,string::const_iterator AEnd, + string::const_iterator B,string::const_iterator BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (tolower_ascii(*A) != tolower_ascii(*B)) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (tolower_ascii(*A) < tolower_ascii(*B)) + return -1; + return 1; +} +#endif + /*}}}*/ +// LookupTag - Lookup the value of a tag in a tagged string /*{{{*/ +// --------------------------------------------------------------------- +/* The format is like those used in package files and the method + communication system */ +std::string LookupTag(const std::string &Message, const char *TagC, const char *Default) +{ + std::string tag = std::string("\n") + TagC + ":"; + if (Default == nullptr) + Default = ""; + if (Message.length() < tag.length()) + return Default; + std::transform(tag.begin(), tag.end(), tag.begin(), tolower_ascii); + auto valuestart = Message.cbegin(); + // maybe the message starts directly with tag + if (Message[tag.length() - 2] == ':') + { + std::string lowstart = std::string("\n") + Message.substr(0, tag.length() - 1); + std::transform(lowstart.begin(), lowstart.end(), lowstart.begin(), tolower_ascii); + if (lowstart == tag) + valuestart = std::next(valuestart, tag.length() - 1); + } + // the tag is somewhere in the message + if (valuestart == Message.cbegin()) + { + auto const tagbegin = std::search(Message.cbegin(), Message.cend(), tag.cbegin(), tag.cend(), + [](char const a, char const b) { return tolower_ascii(a) == b; }); + if (tagbegin == Message.cend()) + return Default; + valuestart = std::next(tagbegin, tag.length()); + } + auto const is_whitespace = [](char const c) { return isspace_ascii(c) != 0 && c != '\n'; }; + auto const is_newline = [](char const c) { return c == '\n'; }; + std::string result; + valuestart = std::find_if_not(valuestart, Message.cend(), is_whitespace); + // is the first line of the value empty? + if (valuestart != Message.cend() && *valuestart == '\n') + { + valuestart = std::next(valuestart); + if (valuestart != Message.cend() && *valuestart == ' ') + valuestart = std::next(valuestart); + } + // extract the value over multiple lines removing trailing whitespace + while (valuestart < Message.cend()) + { + auto const linebreak = std::find_if(valuestart, Message.cend(), is_newline); + auto valueend = std::prev(linebreak); + // skip spaces at the end of the line + while (valueend > valuestart && is_whitespace(*valueend)) + valueend = std::prev(valueend); + // append found line to result + { + std::string tmp(valuestart, std::next(valueend)); + if (tmp != ".") + { + if (result.empty()) + result.assign(std::move(tmp)); + else + result.append(tmp); + } + } + // see if the value is multiline + if (linebreak == Message.cend()) + break; + valuestart = std::next(linebreak); + if (valuestart == Message.cend() || *valuestart != ' ') + break; + result.append("\n"); + // skip the space leading a multiline (Keep all other whitespaces in the value) + valuestart = std::next(valuestart); + } + auto const valueend = result.find_last_not_of("\n"); + if (valueend == std::string::npos) + result.clear(); + else + result.erase(valueend + 1); + return result; +} + /*}}}*/ +// StringToBool - Converts a string into a boolean /*{{{*/ +// --------------------------------------------------------------------- +/* This inspects the string to see if it is true or if it is false and + then returns the result. Several variants on true/false are checked. */ +int StringToBool(const string &Text,int Default) +{ + char *ParseEnd; + int Res = strtol(Text.c_str(),&ParseEnd,0); + // ensure that the entire string was converted by strtol to avoid + // failures on "apt-cache show -a 0ad" where the "0" is converted + const char *TextEnd = Text.c_str()+Text.size(); + if (ParseEnd == TextEnd && Res >= 0 && Res <= 1) + return Res; + + // Check for positives + if (strcasecmp(Text.c_str(),"no") == 0 || + strcasecmp(Text.c_str(),"false") == 0 || + strcasecmp(Text.c_str(),"without") == 0 || + strcasecmp(Text.c_str(),"off") == 0 || + strcasecmp(Text.c_str(),"disable") == 0) + return 0; + + // Check for negatives + if (strcasecmp(Text.c_str(),"yes") == 0 || + strcasecmp(Text.c_str(),"true") == 0 || + strcasecmp(Text.c_str(),"with") == 0 || + strcasecmp(Text.c_str(),"on") == 0 || + strcasecmp(Text.c_str(),"enable") == 0) + return 1; + + return Default; +} + /*}}}*/ +// TimeRFC1123 - Convert a time_t into RFC1123 format /*{{{*/ +// --------------------------------------------------------------------- +/* This converts a time_t into a string time representation that is + year 2000 compliant and timezone neutral */ +string TimeRFC1123(time_t Date) +{ + return TimeRFC1123(Date, false); +} +string TimeRFC1123(time_t Date, bool const NumericTimezone) +{ + struct tm Conv; + if (gmtime_r(&Date, &Conv) == NULL) + return ""; + + auto const posix = std::locale::classic(); + std::ostringstream datestr; + datestr.imbue(posix); + APT::StringView const fmt("%a, %d %b %Y %H:%M:%S"); + std::use_facet<std::time_put<char>>(posix).put( + std::ostreambuf_iterator<char>(datestr), + datestr, ' ', &Conv, fmt.data(), fmt.data() + fmt.size()); + if (NumericTimezone) + datestr << " +0000"; + else + datestr << " GMT"; + return datestr.str(); +} + /*}}}*/ +// ReadMessages - Read messages from the FD /*{{{*/ +// --------------------------------------------------------------------- +/* This pulls full messages from the input FD into the message buffer. + It assumes that messages will not pause during transit so no + fancy buffering is used. + + In particular: this reads blocks from the input until it believes + that it's run out of input text. Each block is terminated by a + double newline ('\n' followed by '\n'). + */ +bool ReadMessages(int Fd, vector<string> &List) +{ + char Buffer[64000]; + // Represents any left-over from the previous iteration of the + // parse loop. (i.e., if a message is split across the end + // of the buffer, it goes here) + string PartialMessage; + + do { + int const Res = read(Fd, Buffer, sizeof(Buffer)); + if (Res < 0 && errno == EINTR) + continue; + + // process we read from has died + if (Res == 0) + return false; + + // No data +#if EAGAIN != EWOULDBLOCK + if (Res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) +#else + if (Res < 0 && errno == EAGAIN) +#endif + return true; + if (Res < 0) + return false; + + // extract the message(s) from the buffer + char const *Start = Buffer; + char const * const End = Buffer + Res; + + char const * NL = (char const *) memchr(Start, '\n', End - Start); + if (NL == NULL) + { + // end of buffer: store what we have so far and read new data in + PartialMessage.append(Start, End - Start); + Start = End; + } + else + ++NL; + + if (PartialMessage.empty() == false && Start < End) + { + // if we start with a new line, see if the partial message we have ended with one + // so that we properly detect records ending between two read() runs + // cases are: \n|\n , \r\n|\r\n and \r\n\r|\n + // the case \r|\n\r\n is handled by the usual double-newline handling + if ((NL - Start) == 1 || ((NL - Start) == 2 && *Start == '\r')) + { + if (APT::String::Endswith(PartialMessage, "\n") || APT::String::Endswith(PartialMessage, "\r\n\r")) + { + PartialMessage.erase(PartialMessage.find_last_not_of("\r\n") + 1); + List.push_back(PartialMessage); + PartialMessage.clear(); + while (NL < End && (*NL == '\n' || *NL == '\r')) ++NL; + Start = NL; + } + } + } + + while (Start < End) { + char const * NL2 = (char const *) memchr(NL, '\n', End - NL); + if (NL2 == NULL) + { + // end of buffer: store what we have so far and read new data in + PartialMessage.append(Start, End - Start); + break; + } + ++NL2; + + // did we find a double newline? + if ((NL2 - NL) == 1 || ((NL2 - NL) == 2 && *NL == '\r')) + { + PartialMessage.append(Start, NL2 - Start); + PartialMessage.erase(PartialMessage.find_last_not_of("\r\n") + 1); + List.push_back(PartialMessage); + PartialMessage.clear(); + while (NL2 < End && (*NL2 == '\n' || *NL2 == '\r')) ++NL2; + Start = NL2; + } + NL = NL2; + } + + // we have read at least one complete message and nothing left + if (PartialMessage.empty() == true) + return true; + + if (WaitFd(Fd) == false) + return false; + } while (true); +} + /*}}}*/ +// MonthConv - Converts a month string into a number /*{{{*/ +// --------------------------------------------------------------------- +/* This was lifted from the boa webserver which lifted it from 'wn-v1.07' + Made it a bit more robust with a few tolower_ascii though. */ +static int MonthConv(char const * const Month) +{ + switch (tolower_ascii(*Month)) + { + case 'a': + return tolower_ascii(Month[1]) == 'p'?3:7; + case 'd': + return 11; + case 'f': + return 1; + case 'j': + if (tolower_ascii(Month[1]) == 'a') + return 0; + return tolower_ascii(Month[2]) == 'n'?5:6; + case 'm': + return tolower_ascii(Month[2]) == 'r'?2:4; + case 'n': + return 10; + case 'o': + return 9; + case 's': + return 8; + + // Pretend it is January.. + default: + return 0; + } +} + /*}}}*/ +// timegm - Internal timegm if the gnu version is not available /*{{{*/ +// --------------------------------------------------------------------- +/* Converts struct tm to time_t, assuming the data in tm is UTC rather + than local timezone (mktime assumes the latter). + + This function is a nonstandard GNU extension that is also present on + the BSDs and maybe other systems. For others we follow the advice of + the manpage of timegm and use his portable replacement. */ +#ifndef HAVE_TIMEGM +static time_t timegm(struct tm *t) +{ + char *tz = getenv("TZ"); + setenv("TZ", "", 1); + tzset(); + time_t ret = mktime(t); + if (tz) + setenv("TZ", tz, 1); + else + unsetenv("TZ"); + tzset(); + return ret; +} +#endif + /*}}}*/ +// RFC1123StrToTime - Converts an HTTP1.1 full date strings into a time_t /*{{{*/ +// --------------------------------------------------------------------- +/* tries to parses a full date as specified in RFC7231 §7.1.1.1 + with one exception: HTTP/1.1 valid dates need to have GMT as timezone. + As we encounter dates from UTC or with a numeric timezone in other places, + we allow them here to to be able to reuse the method. Either way, a date + must be in UTC or parsing will fail. Previous implementations of this + method used to ignore the timezone and assume always UTC. */ +bool RFC1123StrToTime(const char* const str,time_t &time) +{ + unsigned short day = 0; + signed int year = 0; // yes, Y23K problem – we going to worry then… + std::string weekday, month, datespec, timespec, zone; + std::istringstream ss(str); + auto const &posix = std::locale::classic(); + ss.imbue(posix); + ss >> weekday; + // we only superficially check weekday, mostly to avoid accepting localized + // weekdays here and take only its length to decide which datetime format we + // encounter here. The date isn't stored. + std::transform(weekday.begin(), weekday.end(), weekday.begin(), ::tolower); + std::array<char const * const, 7> c_weekdays = {{ "sun", "mon", "tue", "wed", "thu", "fri", "sat" }}; + if (std::find(c_weekdays.begin(), c_weekdays.end(), weekday.substr(0,3)) == c_weekdays.end()) + return false; + + switch (weekday.length()) + { + case 4: + // Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + if (weekday[3] != ',') + return false; + ss >> day >> month >> year >> timespec >> zone; + break; + case 3: + // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + ss >> month >> day >> timespec >> year; + zone = "UTC"; + break; + case 0: + case 1: + case 2: + return false; + default: + // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + if (weekday[weekday.length() - 1] != ',') + return false; + ss >> datespec >> timespec >> zone; + auto const expldate = VectorizeString(datespec, '-'); + if (expldate.size() != 3) + return false; + try { + size_t pos; + day = std::stoi(expldate[0], &pos); + if (pos != expldate[0].length()) + return false; + year = 1900 + std::stoi(expldate[2], &pos); + if (pos != expldate[2].length()) + return false; + strprintf(datespec, "%.4d-%.2d-%.2d", year, MonthConv(expldate[1].c_str()) + 1, day); + } catch (...) { + return false; + } + break; + } + + if (ss.fail() || ss.bad() || !ss.eof()) + return false; + + if (zone != "GMT" && zone != "UTC" && zone != "Z") // RFC 822 + { + // numeric timezones as a should of RFC 1123 and generally preferred + try { + size_t pos; + auto const z = std::stoi(zone, &pos); + if (z != 0 || pos != zone.length()) + return false; + } catch (...) { + return false; + } + } + + if (datespec.empty()) + { + if (month.empty()) + return false; + strprintf(datespec, "%.4d-%.2d-%.2d", year, MonthConv(month.c_str()) + 1, day); + } + + std::string const datetime = datespec + ' ' + timespec; + struct tm Tm; + if (strptime(datetime.c_str(), "%Y-%m-%d %H:%M:%S", &Tm) == nullptr) + return false; + time = timegm(&Tm); + return true; +} + /*}}}*/ +// FTPMDTMStrToTime - Converts a ftp modification date into a time_t /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool FTPMDTMStrToTime(const char* const str,time_t &time) +{ + struct tm Tm; + // MDTM includes no whitespaces but recommend and ignored by strptime + if (strptime(str, "%Y %m %d %H %M %S", &Tm) == NULL) + return false; + + time = timegm(&Tm); + return true; +} + /*}}}*/ +// StrToTime - Converts a string into a time_t /*{{{*/ +// --------------------------------------------------------------------- +/* This handles all 3 popular time formats including RFC 1123, RFC 1036 + and the C library asctime format. It requires the GNU library function + 'timegm' to convert a struct tm in UTC to a time_t. For some bizzar + reason the C library does not provide any such function :< This also + handles the weird, but unambiguous FTP time format*/ +bool StrToTime(const string &Val,time_t &Result) +{ + struct tm Tm; + char Month[10]; + + // Skip the day of the week + const char *I = strchr(Val.c_str(), ' '); + + // Handle RFC 1123 time + Month[0] = 0; + if (sscanf(I," %2d %3s %4d %2d:%2d:%2d GMT",&Tm.tm_mday,Month,&Tm.tm_year, + &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) != 6) + { + // Handle RFC 1036 time + if (sscanf(I," %2d-%3s-%3d %2d:%2d:%2d GMT",&Tm.tm_mday,Month, + &Tm.tm_year,&Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) == 6) + Tm.tm_year += 1900; + else + { + // asctime format + if (sscanf(I," %3s %2d %2d:%2d:%2d %4d",Month,&Tm.tm_mday, + &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec,&Tm.tm_year) != 6) + { + // 'ftp' time + if (sscanf(Val.c_str(),"%4d%2d%2d%2d%2d%2d",&Tm.tm_year,&Tm.tm_mon, + &Tm.tm_mday,&Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) != 6) + return false; + Tm.tm_mon--; + } + } + } + + Tm.tm_isdst = 0; + if (Month[0] != 0) + Tm.tm_mon = MonthConv(Month); + else + Tm.tm_mon = 0; // we don't have a month, so pick something + Tm.tm_year -= 1900; + + // Convert to local time and then to GMT + Result = timegm(&Tm); + return true; +} + /*}}}*/ +// StrToNum - Convert a fixed length string to a number /*{{{*/ +// --------------------------------------------------------------------- +/* This is used in decoding the crazy fixed length string headers in + tar and ar files. */ +bool StrToNum(const char *Str,unsigned long &Res,unsigned Len,unsigned Base) +{ + char S[30]; + if (Len >= sizeof(S)) + return false; + memcpy(S,Str,Len); + S[Len] = 0; + + // All spaces is a zero + Res = 0; + unsigned I; + for (I = 0; S[I] == ' '; I++); + if (S[I] == 0) + return true; + + char *End; + Res = strtoul(S,&End,Base); + if (End == S) + return false; + + return true; +} + /*}}}*/ +// StrToNum - Convert a fixed length string to a number /*{{{*/ +// --------------------------------------------------------------------- +/* This is used in decoding the crazy fixed length string headers in + tar and ar files. */ +bool StrToNum(const char *Str,unsigned long long &Res,unsigned Len,unsigned Base) +{ + char S[30]; + if (Len >= sizeof(S)) + return false; + memcpy(S,Str,Len); + S[Len] = 0; + + // All spaces is a zero + Res = 0; + unsigned I; + for (I = 0; S[I] == ' '; I++); + if (S[I] == 0) + return true; + + char *End; + Res = strtoull(S,&End,Base); + if (End == S) + return false; + + return true; +} + /*}}}*/ + +// Base256ToNum - Convert a fixed length binary to a number /*{{{*/ +// --------------------------------------------------------------------- +/* This is used in decoding the 256bit encoded fixed length fields in + tar files */ +bool Base256ToNum(const char *Str,unsigned long long &Res,unsigned int Len) +{ + if ((Str[0] & 0x80) == 0) + return false; + else + { + Res = Str[0] & 0x7F; + for(unsigned int i = 1; i < Len; ++i) + Res = (Res<<8) + Str[i]; + return true; + } +} + /*}}}*/ +// Base256ToNum - Convert a fixed length binary to a number /*{{{*/ +// --------------------------------------------------------------------- +/* This is used in decoding the 256bit encoded fixed length fields in + tar files */ +bool Base256ToNum(const char *Str,unsigned long &Res,unsigned int Len) +{ + unsigned long long Num = 0; + bool rc; + + rc = Base256ToNum(Str, Num, Len); + // rudimentary check for overflow (Res = ulong, Num = ulonglong) + Res = Num; + if (Res != Num) + return false; + + return rc; +} + /*}}}*/ +// HexDigit - Convert a hex character into an integer /*{{{*/ +// --------------------------------------------------------------------- +/* Helper for Hex2Num */ +static int HexDigit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + /*}}}*/ +// Hex2Num - Convert a long hex number into a buffer /*{{{*/ +// --------------------------------------------------------------------- +/* The length of the buffer must be exactly 1/2 the length of the string. */ +bool Hex2Num(const string &Str,unsigned char *Num,unsigned int Length) +{ + return Hex2Num(APT::StringView(Str), Num, Length); +} + +bool Hex2Num(const APT::StringView Str,unsigned char *Num,unsigned int Length) +{ + if (Str.length() != Length*2) + return false; + + // Convert each digit. We store it in the same order as the string + int J = 0; + for (auto I = Str.begin(); I != Str.end();J++, I += 2) + { + int first_half = HexDigit(I[0]); + int second_half; + if (first_half < 0) + return false; + + second_half = HexDigit(I[1]); + if (second_half < 0) + return false; + Num[J] = first_half << 4; + Num[J] += second_half; + } + + return true; +} + /*}}}*/ +// TokSplitString - Split a string up by a given token /*{{{*/ +// --------------------------------------------------------------------- +/* This is intended to be a faster splitter, it does not use dynamic + memories. Input is changed to insert nulls at each token location. */ +bool TokSplitString(char Tok,char *Input,char **List, + unsigned long ListMax) +{ + // Strip any leading spaces + char *Start = Input; + char *Stop = Start + strlen(Start); + for (; *Start != 0 && isspace(*Start) != 0; Start++); + + unsigned long Count = 0; + char *Pos = Start; + while (Pos != Stop) + { + // Skip to the next Token + for (; Pos != Stop && *Pos != Tok; Pos++); + + // Back remove spaces + char *End = Pos; + for (; End > Start && (End[-1] == Tok || isspace(End[-1]) != 0); End--); + *End = 0; + + List[Count++] = Start; + if (Count >= ListMax) + { + List[Count-1] = 0; + return false; + } + + // Advance pos + for (; Pos != Stop && (*Pos == Tok || isspace(*Pos) != 0 || *Pos == 0); Pos++); + Start = Pos; + } + + List[Count] = 0; + return true; +} + /*}}}*/ +// VectorizeString - Split a string up into a vector of strings /*{{{*/ +// --------------------------------------------------------------------- +/* This can be used to split a given string up into a vector, so the + propose is the same as in the method above and this one is a bit slower + also, but the advantage is that we have an iteratable vector */ +vector<string> VectorizeString(string const &haystack, char const &split) +{ + vector<string> exploded; + if (haystack.empty() == true) + return exploded; + string::const_iterator start = haystack.begin(); + string::const_iterator end = start; + do { + for (; end != haystack.end() && *end != split; ++end); + exploded.push_back(string(start, end)); + start = end + 1; + } while (end != haystack.end() && (++end) != haystack.end()); + return exploded; +} + /*}}}*/ +// StringSplit - split a string into a string vector by token /*{{{*/ +// --------------------------------------------------------------------- +/* See header for details. + */ +vector<string> StringSplit(std::string const &s, std::string const &sep, + unsigned int maxsplit) +{ + vector<string> split; + size_t start, pos; + + // no separator given, this is bogus + if(sep.size() == 0) + return split; + + start = pos = 0; + while (pos != string::npos) + { + pos = s.find(sep, start); + split.push_back(s.substr(start, pos-start)); + + // if maxsplit is reached, the remaining string is the last item + if(split.size() >= maxsplit) + { + split[split.size()-1] = s.substr(start); + break; + } + start = pos+sep.size(); + } + return split; +} + /*}}}*/ +// RegexChoice - Simple regex list/list matcher /*{{{*/ +// --------------------------------------------------------------------- +/* */ +unsigned long RegexChoice(RxChoiceList *Rxs,const char **ListBegin, + const char **ListEnd) +{ + for (RxChoiceList *R = Rxs; R->Str != 0; R++) + R->Hit = false; + + unsigned long Hits = 0; + for (; ListBegin < ListEnd; ++ListBegin) + { + // Check if the name is a regex + const char *I; + bool Regex = true; + for (I = *ListBegin; *I != 0; I++) + if (*I == '.' || *I == '?' || *I == '*' || *I == '|') + break; + if (*I == 0) + Regex = false; + + // Compile the regex pattern + regex_t Pattern; + if (Regex == true) + if (regcomp(&Pattern,*ListBegin,REG_EXTENDED | REG_ICASE | + REG_NOSUB) != 0) + Regex = false; + + // Search the list + bool Done = false; + for (RxChoiceList *R = Rxs; R->Str != 0; R++) + { + if (R->Str[0] == 0) + continue; + + if (strcasecmp(R->Str,*ListBegin) != 0) + { + if (Regex == false) + continue; + if (regexec(&Pattern,R->Str,0,0,0) != 0) + continue; + } + Done = true; + + if (R->Hit == false) + Hits++; + + R->Hit = true; + } + + if (Regex == true) + regfree(&Pattern); + + if (Done == false) + _error->Warning(_("Selection %s not found"),*ListBegin); + } + + return Hits; +} + /*}}}*/ +// {str,io}printf - C format string outputter to C++ strings/iostreams /*{{{*/ +// --------------------------------------------------------------------- +/* This is used to make the internationalization strings easier to translate + and to allow reordering of parameters */ +static bool iovprintf(ostream &out, const char *format, + va_list &args, ssize_t &size) { + char *S = (char*)malloc(size); + ssize_t const n = vsnprintf(S, size, format, args); + if (n > -1 && n < size) { + out << S; + free(S); + return true; + } else { + if (n > -1) + size = n + 1; + else + size *= 2; + } + free(S); + return false; +} +void ioprintf(ostream &out,const char *format,...) +{ + va_list args; + ssize_t size = 400; + while (true) { + bool ret; + va_start(args,format); + ret = iovprintf(out, format, args, size); + va_end(args); + if (ret == true) + return; + } +} +void strprintf(string &out,const char *format,...) +{ + va_list args; + ssize_t size = 400; + std::ostringstream outstr; + while (true) { + bool ret; + va_start(args,format); + ret = iovprintf(outstr, format, args, size); + va_end(args); + if (ret == true) + break; + } + out = outstr.str(); +} + /*}}}*/ +// safe_snprintf - Safer snprintf /*{{{*/ +// --------------------------------------------------------------------- +/* This is a snprintf that will never (ever) go past 'End' and returns a + pointer to the end of the new string. The returned string is always null + terminated unless Buffer == end. This is a better alterantive to using + consecutive snprintfs. */ +char *safe_snprintf(char *Buffer,char *End,const char *Format,...) +{ + va_list args; + int Did; + + if (End <= Buffer) + return End; + va_start(args,Format); + Did = vsnprintf(Buffer,End - Buffer,Format,args); + va_end(args); + + if (Did < 0 || Buffer + Did > End) + return End; + return Buffer + Did; +} + /*}}}*/ +// StripEpoch - Remove the version "epoch" from a version string /*{{{*/ +// --------------------------------------------------------------------- +string StripEpoch(const string &VerStr) +{ + size_t i = VerStr.find(":"); + if (i == string::npos) + return VerStr; + return VerStr.substr(i+1); +} + /*}}}*/ + +// tolower_ascii - tolower() function that ignores the locale /*{{{*/ +// --------------------------------------------------------------------- +/* This little function is the most called method we have and tries + therefore to do the absolute minimum - and is notable faster than + standard tolower/toupper and as a bonus avoids problems with different + locales - we only operate on ascii chars anyway. */ +#undef tolower_ascii +int tolower_ascii(int const c) APT_PURE APT_COLD; +int tolower_ascii(int const c) +{ + return tolower_ascii_inline(c); +} + /*}}}*/ + +// isspace_ascii - isspace() function that ignores the locale /*{{{*/ +// --------------------------------------------------------------------- +/* This little function is one of the most called methods we have and tries + therefore to do the absolute minimum - and is notable faster than + standard isspace() and as a bonus avoids problems with different + locales - we only operate on ascii chars anyway. */ +#undef isspace_ascii +int isspace_ascii(int const c) APT_PURE APT_COLD; +int isspace_ascii(int const c) +{ + return isspace_ascii_inline(c); +} + /*}}}*/ + +// CheckDomainList - See if Host is in a , separate list /*{{{*/ +// --------------------------------------------------------------------- +/* The domain list is a comma separate list of domains that are suffix + matched against the argument */ +bool CheckDomainList(const string &Host,const string &List) +{ + string::const_iterator Start = List.begin(); + for (string::const_iterator Cur = List.begin(); Cur <= List.end(); ++Cur) + { + if (Cur < List.end() && *Cur != ',') + continue; + + // Match the end of the string.. + if ((Host.size() >= (unsigned)(Cur - Start)) && + Cur - Start != 0 && + stringcasecmp(Host.end() - (Cur - Start),Host.end(),Start,Cur) == 0) + return true; + + Start = Cur + 1; + } + return false; +} + /*}}}*/ +// strv_length - Return the length of a NULL-terminated string array /*{{{*/ +// --------------------------------------------------------------------- +/* */ +size_t strv_length(const char **str_array) +{ + size_t i; + for (i=0; str_array[i] != NULL; i++) + /* nothing */ + ; + return i; +} + /*}}}*/ +// DeEscapeString - unescape (\0XX and \xXX) from a string /*{{{*/ +// --------------------------------------------------------------------- +/* */ +string DeEscapeString(const string &input) +{ + char tmp[3]; + string::const_iterator it; + string output; + for (it = input.begin(); it != input.end(); ++it) + { + // just copy non-escape chars + if (*it != '\\') + { + output += *it; + continue; + } + + // deal with double escape + if (*it == '\\' && + (it + 1 < input.end()) && it[1] == '\\') + { + // copy + output += *it; + // advance iterator one step further + ++it; + continue; + } + + // ensure we have a char to read + if (it + 1 == input.end()) + continue; + + // read it + ++it; + switch (*it) + { + case '0': + if (it + 2 <= input.end()) { + tmp[0] = it[1]; + tmp[1] = it[2]; + tmp[2] = 0; + output += (char)strtol(tmp, 0, 8); + it += 2; + } + break; + case 'x': + if (it + 2 <= input.end()) { + tmp[0] = it[1]; + tmp[1] = it[2]; + tmp[2] = 0; + output += (char)strtol(tmp, 0, 16); + it += 2; + } + break; + default: + // FIXME: raise exception here? + break; + } + } + return output; +} + /*}}}*/ +// URI::CopyFrom - Copy from an object /*{{{*/ +// --------------------------------------------------------------------- +/* This parses the URI into all of its components */ +void URI::CopyFrom(const string &U) +{ + string::const_iterator I = U.begin(); + + // Locate the first colon, this separates the scheme + for (; I < U.end() && *I != ':' ; ++I); + string::const_iterator FirstColon = I; + + /* Determine if this is a host type URI with a leading double // + and then search for the first single / */ + string::const_iterator SingleSlash = I; + if (I + 3 < U.end() && I[1] == '/' && I[2] == '/') + SingleSlash += 3; + + /* Find the / indicating the end of the hostname, ignoring /'s in the + square brackets */ + bool InBracket = false; + for (; SingleSlash < U.end() && (*SingleSlash != '/' || InBracket == true); ++SingleSlash) + { + if (*SingleSlash == '[') + InBracket = true; + if (InBracket == true && *SingleSlash == ']') + InBracket = false; + } + + if (SingleSlash > U.end()) + SingleSlash = U.end(); + + // We can now write the access and path specifiers + Access.assign(U.begin(),FirstColon); + if (SingleSlash != U.end()) + Path.assign(SingleSlash,U.end()); + if (Path.empty() == true) + Path = "/"; + + // Now we attempt to locate a user:pass@host fragment + if (FirstColon + 2 <= U.end() && FirstColon[1] == '/' && FirstColon[2] == '/') + FirstColon += 3; + else + FirstColon += 1; + if (FirstColon >= U.end()) + return; + + if (FirstColon > SingleSlash) + FirstColon = SingleSlash; + + // Find the colon... + I = FirstColon + 1; + if (I > SingleSlash) + I = SingleSlash; + + // Search for the @ separating user:pass from host + auto const RevAt = std::find( + std::string::const_reverse_iterator(SingleSlash), + std::string::const_reverse_iterator(I), '@'); + string::const_iterator const At = RevAt.base() == I ? SingleSlash : std::prev(RevAt.base()); + // and then look for the colon between user and pass + string::const_iterator const SecondColon = std::find(I, At, ':'); + + // Now write the host and user/pass + if (At == SingleSlash) + { + if (FirstColon < SingleSlash) + Host.assign(FirstColon,SingleSlash); + } + else + { + Host.assign(At+1,SingleSlash); + // username and password must be encoded (RFC 3986) + User.assign(DeQuoteString(FirstColon,SecondColon)); + if (SecondColon < At) + Password.assign(DeQuoteString(SecondColon+1,At)); + } + + // Now we parse the RFC 2732 [] hostnames. + unsigned long PortEnd = 0; + InBracket = false; + for (unsigned I = 0; I != Host.length();) + { + if (Host[I] == '[') + { + InBracket = true; + Host.erase(I,1); + continue; + } + + if (InBracket == true && Host[I] == ']') + { + InBracket = false; + Host.erase(I,1); + PortEnd = I; + continue; + } + I++; + } + + // Tsk, weird. + if (InBracket == true) + { + Host.clear(); + return; + } + + // Now we parse off a port number from the hostname + Port = 0; + string::size_type Pos = Host.rfind(':'); + if (Pos == string::npos || Pos < PortEnd) + return; + + Port = atoi(string(Host,Pos+1).c_str()); + Host.assign(Host,0,Pos); +} + /*}}}*/ +// URI::operator string - Convert the URI to a string /*{{{*/ +// --------------------------------------------------------------------- +/* */ +URI::operator string() +{ + std::stringstream Res; + + if (Access.empty() == false) + Res << Access << ':'; + + if (Host.empty() == false) + { + if (Access.empty() == false) + Res << "//"; + + if (User.empty() == false) + { + // FIXME: Technically userinfo is permitted even less + // characters than these, but this is not conveniently + // expressed with a blacklist. + Res << QuoteString(User, ":/?#[]@"); + if (Password.empty() == false) + Res << ":" << QuoteString(Password, ":/?#[]@"); + Res << "@"; + } + + // Add RFC 2732 escaping characters + if (Access.empty() == false && Host.find_first_of("/:") != string::npos) + Res << '[' << Host << ']'; + else + Res << Host; + + if (Port != 0) + Res << ':' << std::to_string(Port); + } + + if (Path.empty() == false) + { + if (Path[0] != '/') + Res << "/" << Path; + else + Res << Path; + } + + return Res.str(); +} + /*}}}*/ +// URI::SiteOnly - Return the schema and site for the URI /*{{{*/ +string URI::SiteOnly(const string &URI) +{ + ::URI U(URI); + U.User.clear(); + U.Password.clear(); + U.Path.clear(); + return U; +} + /*}}}*/ +// URI::ArchiveOnly - Return the schema, site and cleaned path for the URI /*{{{*/ +string URI::ArchiveOnly(const string &URI) +{ + ::URI U(URI); + U.User.clear(); + U.Password.clear(); + if (U.Path.empty() == false && U.Path[U.Path.length() - 1] == '/') + U.Path.erase(U.Path.length() - 1); + return U; +} + /*}}}*/ +// URI::NoUserPassword - Return the schema, site and path for the URI /*{{{*/ +string URI::NoUserPassword(const string &URI) +{ + ::URI U(URI); + U.User.clear(); + U.Password.clear(); + return U; +} + /*}}}*/ diff --git a/apt-pkg/contrib/strutl.h b/apt-pkg/contrib/strutl.h new file mode 100644 index 0000000..9f74f8c --- /dev/null +++ b/apt-pkg/contrib/strutl.h @@ -0,0 +1,245 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + String Util - These are some useful string functions + + _strstrip is a function to remove whitespace from the front and end + of a string. + + This source is placed in the Public Domain, do with it what you will + It was originally written by Jason Gunthorpe <jgg@gpu.srv.ualberta.ca> + + ##################################################################### */ + /*}}}*/ +#ifndef STRUTL_H +#define STRUTL_H + +#include <cstring> +#include <iostream> +#include <limits> +#include <string> +#include <vector> +#ifdef APT_PKG_EXPOSE_STRING_VIEW +#include <apt-pkg/string_view.h> +#endif +#include <stddef.h> +#include <time.h> + +#include "macros.h" + +#ifndef APT_10_CLEANER_HEADERS +#include <stdlib.h> +#endif +#ifndef APT_8_CLEANER_HEADERS +using std::string; +using std::vector; +using std::ostream; +#endif + +namespace APT { + namespace String { + std::string Strip(const std::string &s); + bool Endswith(const std::string &s, const std::string &ending); + bool Startswith(const std::string &s, const std::string &starting); + std::string Join(std::vector<std::string> list, const std::string &sep); + + } +} + + +bool UTF8ToCodeset(const char *codeset, const std::string &orig, std::string *dest); +char *_strstrip(char *String); +char *_strrstrip(char *String); // right strip only +char *_strtabexpand(char *String,size_t Len); +bool ParseQuoteWord(const char *&String,std::string &Res); +bool ParseCWord(const char *&String,std::string &Res); +std::string QuoteString(const std::string &Str,const char *Bad); +std::string DeQuoteString(const std::string &Str); +std::string DeQuoteString(std::string::const_iterator const &begin, std::string::const_iterator const &end); + +// unescape (\0XX and \xXX) from a string +std::string DeEscapeString(const std::string &input); + +std::string SizeToStr(double Bytes); +std::string TimeToStr(unsigned long Sec); +std::string Base64Encode(const std::string &Str); +std::string OutputInDepth(const unsigned long Depth, const char* Separator=" "); +std::string URItoFileName(const std::string &URI); +APT_DEPRECATED_MSG("Specify if GMT is required or a numeric timezone can be used") std::string TimeRFC1123(time_t Date); +/** returns a datetime string as needed by HTTP/1.1 and Debian files. + * + * Note: The date will always be represented in a UTC timezone + * + * @param Date to be represented as a string + * @param NumericTimezone is preferred in general, but HTTP/1.1 requires the use + * of GMT as timezone instead. \b true means that the timezone should be denoted + * as "+0000" while \b false uses "GMT". + */ +std::string TimeRFC1123(time_t Date, bool const NumericTimezone); +/** parses time as needed by HTTP/1.1 and Debian files. + * + * HTTP/1.1 prefers dates in RFC1123 format (but the other two obsolete date formats + * are supported to) and e.g. Release files use the same format in Date & Valid-Until + * fields. + * + * Note: datetime strings need to be in UTC timezones (GMT, UTC, Z, +/-0000) to be + * parsed. Other timezones will be rejected as invalid. Previous implementations + * accepted other timezones, but treated them as UTC. + * + * @param str is the datetime string to parse + * @param[out] time will be the seconds since epoch of the given datetime if + * parsing is successful, undefined otherwise. + * @return \b true if parsing was successful, otherwise \b false. + */ +bool RFC1123StrToTime(const char* const str,time_t &time) APT_MUSTCHECK; +bool FTPMDTMStrToTime(const char* const str,time_t &time) APT_MUSTCHECK; +APT_DEPRECATED_MSG("Use RFC1123StrToTime or FTPMDTMStrToTime as needed instead") bool StrToTime(const std::string &Val,time_t &Result); +std::string LookupTag(const std::string &Message,const char *Tag,const char *Default = 0); +int StringToBool(const std::string &Text,int Default = -1); +bool ReadMessages(int Fd, std::vector<std::string> &List); +bool StrToNum(const char *Str,unsigned long &Res,unsigned Len,unsigned Base = 0); +bool StrToNum(const char *Str,unsigned long long &Res,unsigned Len,unsigned Base = 0); +bool Base256ToNum(const char *Str,unsigned long &Res,unsigned int Len); +bool Base256ToNum(const char *Str,unsigned long long &Res,unsigned int Len); +bool Hex2Num(const std::string &Str,unsigned char *Num,unsigned int Length); +#ifdef APT_PKG_EXPOSE_STRING_VIEW +APT_HIDDEN bool Hex2Num(const APT::StringView Str,unsigned char *Num,unsigned int Length); +#endif +// input changing string split +bool TokSplitString(char Tok,char *Input,char **List, + unsigned long ListMax); + +// split a given string by a char +std::vector<std::string> VectorizeString(std::string const &haystack, char const &split) APT_PURE; + +/* \brief Return a vector of strings from string "input" where "sep" + * is used as the delimiter string. + * + * \param input The input string. + * + * \param sep The separator to use. + * + * \param maxsplit (optional) The maximum amount of splitting that + * should be done . + * + * The optional "maxsplit" argument can be used to limit the splitting, + * if used the string is only split on maxsplit places and the last + * item in the vector contains the remainder string. + */ +std::vector<std::string> StringSplit(std::string const &input, + std::string const &sep, + unsigned int maxsplit=std::numeric_limits<unsigned int>::max()) APT_PURE; + +void ioprintf(std::ostream &out,const char *format,...) APT_PRINTF(2); +void strprintf(std::string &out,const char *format,...) APT_PRINTF(2); +char *safe_snprintf(char *Buffer,char *End,const char *Format,...) APT_PRINTF(3); +bool CheckDomainList(const std::string &Host, const std::string &List); + +/* Do some compat mumbo jumbo */ +#define tolower_ascii tolower_ascii_inline +#define isspace_ascii isspace_ascii_inline + +APT_PURE APT_HOT +static inline int tolower_ascii_unsafe(int const c) +{ + return c | 0x20; +} +APT_PURE APT_HOT +static inline int tolower_ascii_inline(int const c) +{ + return (c >= 'A' && c <= 'Z') ? c + 32 : c; +} +APT_PURE APT_HOT +static inline int isspace_ascii_inline(int const c) +{ + // 9='\t',10='\n',11='\v',12='\f',13='\r',32=' ' + return (c >= 9 && c <= 13) || c == ' '; +} + +std::string StripEpoch(const std::string &VerStr); + +#define APT_MKSTRCMP(name,func) \ +inline APT_PURE int name(const char *A,const char *B) {return func(A,A+strlen(A),B,B+strlen(B));} \ +inline APT_PURE int name(const char *A,const char *AEnd,const char *B) {return func(A,AEnd,B,B+strlen(B));} \ +inline APT_PURE int name(const std::string& A,const char *B) {return func(A.c_str(),A.c_str()+A.length(),B,B+strlen(B));} \ +inline APT_PURE int name(const std::string& A,const std::string& B) {return func(A.c_str(),A.c_str()+A.length(),B.c_str(),B.c_str()+B.length());} \ +inline APT_PURE int name(const std::string& A,const char *B,const char *BEnd) {return func(A.c_str(),A.c_str()+A.length(),B,BEnd);} + +#define APT_MKSTRCMP2(name,func) \ +inline APT_PURE int name(const char *A,const char *AEnd,const char *B) {return func(A,AEnd,B,B+strlen(B));} \ +inline APT_PURE int name(const std::string& A,const char *B) {return func(A.begin(),A.end(),B,B+strlen(B));} \ +inline APT_PURE int name(const std::string& A,const std::string& B) {return func(A.begin(),A.end(),B.begin(),B.end());} \ +inline APT_PURE int name(const std::string& A,const char *B,const char *BEnd) {return func(A.begin(),A.end(),B,BEnd);} + +int APT_PURE stringcmp(const char *A,const char *AEnd,const char *B,const char *BEnd); +int APT_PURE stringcasecmp(const char *A,const char *AEnd,const char *B,const char *BEnd); + +/* We assume that GCC 3 indicates that libstdc++3 is in use too. In that + case the definition of string::const_iterator is not the same as + const char * and we need these extra functions */ +#if __GNUC__ >= 3 +int APT_PURE stringcmp(std::string::const_iterator A,std::string::const_iterator AEnd, + const char *B,const char *BEnd); +int APT_PURE stringcmp(std::string::const_iterator A,std::string::const_iterator AEnd, + std::string::const_iterator B,std::string::const_iterator BEnd); +int APT_PURE stringcasecmp(std::string::const_iterator A,std::string::const_iterator AEnd, + const char *B,const char *BEnd); +int APT_PURE stringcasecmp(std::string::const_iterator A,std::string::const_iterator AEnd, + std::string::const_iterator B,std::string::const_iterator BEnd); + +inline APT_PURE int stringcmp(std::string::const_iterator A,std::string::const_iterator Aend,const char *B) {return stringcmp(A,Aend,B,B+strlen(B));} +inline APT_PURE int stringcasecmp(std::string::const_iterator A,std::string::const_iterator Aend,const char *B) {return stringcasecmp(A,Aend,B,B+strlen(B));} +#endif + +APT_MKSTRCMP2(stringcmp,stringcmp) +APT_MKSTRCMP2(stringcasecmp,stringcasecmp) + +// Return the length of a NULL-terminated string array +size_t APT_PURE strv_length(const char **str_array); + + +inline const char *DeNull(const char *s) {return (s == 0?"(null)":s);} + +class URI +{ + void CopyFrom(const std::string &From); + + public: + + std::string Access; + std::string User; + std::string Password; + std::string Host; + std::string Path; + unsigned int Port; + + operator std::string(); + inline void operator =(const std::string &From) {CopyFrom(From);} + inline bool empty() {return Access.empty();}; + static std::string SiteOnly(const std::string &URI); + static std::string ArchiveOnly(const std::string &URI); + static std::string NoUserPassword(const std::string &URI); + + URI(std::string Path) {CopyFrom(Path);} + URI() : Port(0) {} +}; + +struct SubstVar +{ + const char *Subst; + const std::string *Contents; +}; +std::string SubstVar(std::string Str,const struct SubstVar *Vars); +std::string SubstVar(const std::string &Str,const std::string &Subst,const std::string &Contents); + +struct RxChoiceList +{ + void *UserData; + const char *Str; + bool Hit; +}; +unsigned long RegexChoice(RxChoiceList *Rxs,const char **ListBegin, + const char **ListEnd); + +#endif diff --git a/apt-pkg/contrib/weakptr.h b/apt-pkg/contrib/weakptr.h new file mode 100644 index 0000000..8de727d --- /dev/null +++ b/apt-pkg/contrib/weakptr.h @@ -0,0 +1,64 @@ +/* weakptr.h - An object which supports weak pointers. + * + * Copyright (C) 2010 Julian Andres Klode <jak@debian.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#ifndef WEAK_POINTER_H +#define WEAK_POINTER_H + +#include <set> +#include <stddef.h> + +/** + * Class for objects providing support for weak pointers. + * + * This class allows for the registration of certain pointers as weak, + * which will cause them to be set to NULL when the destructor of the + * object is called. + */ +class WeakPointable { +private: + std::set<WeakPointable**> pointers; + +public: + + /** + * Add a new weak pointer. + */ + inline void AddWeakPointer(WeakPointable** weakptr) { + pointers.insert(weakptr); + } + + /** + * Remove the weak pointer from the list of weak pointers. + */ + inline void RemoveWeakPointer(WeakPointable **weakptr) { + pointers.erase(weakptr); + } + + /** + * Deconstruct the object, set all weak pointers to NULL. + */ + ~WeakPointable() { + std::set<WeakPointable**>::iterator iter = pointers.begin(); + while (iter != pointers.end()) + **(iter++) = NULL; + } +}; + +#endif // WEAK_POINTER_H diff --git a/apt-pkg/deb/debindexfile.cc b/apt-pkg/deb/debindexfile.cc new file mode 100644 index 0000000..25e0a33 --- /dev/null +++ b/apt-pkg/deb/debindexfile.cc @@ -0,0 +1,431 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Debian Specific sources.list types and the three sorts of Debian + index files. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/debindexfile.h> +#include <apt-pkg/deblistparser.h> +#include <apt-pkg/debrecords.h> +#include <apt-pkg/debsrcrecords.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/srcrecords.h> + +#include <iostream> +#include <memory> +#include <sstream> +#include <string> +#include <stdio.h> + +#include <sys/stat.h> +#include <unistd.h> + /*}}}*/ + +// Sources Index /*{{{*/ +debSourcesIndex::debSourcesIndex(IndexTarget const &Target,bool const Trusted) : + pkgDebianIndexTargetFile(Target, Trusted), d(NULL) +{ +} +std::string debSourcesIndex::SourceInfo(pkgSrcRecords::Parser const &Record, + pkgSrcRecords::File const &File) const +{ + // The result looks like: http://foo/debian/ stable/main src 1.1.1 (dsc) + std::string Res = Target.Description; + Res.erase(Target.Description.rfind(' ')); + + Res += " "; + Res += Record.Package(); + Res += " "; + Res += Record.Version(); + if (File.Type.empty() == false) + Res += " (" + File.Type + ")"; + return Res; +} +pkgSrcRecords::Parser *debSourcesIndex::CreateSrcParser() const +{ + std::string const SourcesURI = IndexFileName(); + if (FileExists(SourcesURI)) + return new debSrcRecordParser(SourcesURI, this); + return NULL; +} +bool debSourcesIndex::OpenListFile(FileFd &, std::string const &) +{ + return true; +} +pkgCacheListParser * debSourcesIndex::CreateListParser(FileFd &) +{ + return nullptr; +} +uint8_t debSourcesIndex::GetIndexFlags() const +{ + return 0; +} + /*}}}*/ +// Packages Index /*{{{*/ +debPackagesIndex::debPackagesIndex(IndexTarget const &Target, bool const Trusted) : + pkgDebianIndexTargetFile(Target, Trusted), d(NULL) +{ +} +std::string debPackagesIndex::ArchiveInfo(pkgCache::VerIterator const &Ver) const +{ + std::string Res = Target.Description; + { + auto const space = Target.Description.rfind(' '); + if (space != std::string::npos) + Res.erase(space); + } + + Res += " "; + Res += Ver.ParentPkg().Name(); + Res += " "; + std::string const Dist = Target.Option(IndexTarget::RELEASE); + if (Dist.empty() == false && Dist[Dist.size() - 1] != '/') + Res.append(Ver.Arch()).append(" "); + Res += Ver.VerStr(); + return Res; +} +uint8_t debPackagesIndex::GetIndexFlags() const +{ + return 0; +} + /*}}}*/ +// Translation-* Index /*{{{*/ +debTranslationsIndex::debTranslationsIndex(IndexTarget const &Target) : + pkgDebianIndexTargetFile(Target, true), d(NULL) +{} +bool debTranslationsIndex::HasPackages() const +{ + return Exists(); +} +bool debTranslationsIndex::OpenListFile(FileFd &Pkg, std::string const &FileName) +{ + if (FileExists(FileName)) + return pkgDebianIndexTargetFile::OpenListFile(Pkg, FileName); + return true; +} +uint8_t debTranslationsIndex::GetIndexFlags() const +{ + return pkgCache::Flag::NotSource | pkgCache::Flag::NoPackages; +} +std::string debTranslationsIndex::GetArchitecture() const +{ + return std::string(); +} +pkgCacheListParser * debTranslationsIndex::CreateListParser(FileFd &Pkg) +{ + if (Pkg.IsOpen() == false) + return nullptr; + _error->PushToStack(); + std::unique_ptr<pkgCacheListParser> Parser(new debTranslationsParser(&Pkg)); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + return newError ? nullptr : Parser.release(); +} + /*}}}*/ +// dpkg/status Index /*{{{*/ +debStatusIndex::debStatusIndex(std::string const &File) : pkgDebianIndexRealFile(File, true), d(NULL) +{ +} +std::string debStatusIndex::GetArchitecture() const +{ + return std::string(); +} +std::string debStatusIndex::GetComponent() const +{ + return "now"; +} +uint8_t debStatusIndex::GetIndexFlags() const +{ + return pkgCache::Flag::NotSource; +} + +pkgCacheListParser * debStatusIndex::CreateListParser(FileFd &Pkg) +{ + if (Pkg.IsOpen() == false) + return nullptr; + _error->PushToStack(); + std::unique_ptr<pkgCacheListParser> Parser(new debStatusListParser(&Pkg)); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + return newError ? nullptr : Parser.release(); +} + /*}}}*/ +// DebPkgFile Index - a single .deb file as an index /*{{{*/ +debDebPkgFileIndex::debDebPkgFileIndex(std::string const &DebFile) + : pkgDebianIndexRealFile(DebFile, true), d(NULL), DebFile(DebFile) +{ +} +bool debDebPkgFileIndex::GetContent(std::ostream &content, std::string const &debfile) +{ + struct stat Buf; + if (stat(debfile.c_str(), &Buf) != 0) + return false; + + // get the control data out of the deb file via dpkg-deb -I + std::string dpkg = _config->Find("Dir::Bin::dpkg","dpkg-deb"); + std::vector<const char *> Args; + Args.push_back(dpkg.c_str()); + Args.push_back("-I"); + Args.push_back(debfile.c_str()); + Args.push_back("control"); + Args.push_back(NULL); + FileFd PipeFd; + pid_t Child; + if(Popen((const char**)&Args[0], PipeFd, Child, FileFd::ReadOnly) == false) + return _error->Error("Popen failed"); + + std::string line; + bool first_line_seen = false; + while (PipeFd.ReadLine(line)) + { + if (first_line_seen == false) + { + if (line.empty()) + continue; + first_line_seen = true; + } + else if (line.empty()) + break; + content << line << "\n"; + } + content << "Filename: " << debfile << "\n"; + content << "Size: " << std::to_string(Buf.st_size) << "\n"; + ExecWait(Child, "Popen"); + + return true; +} +bool debDebPkgFileIndex::OpenListFile(FileFd &Pkg, std::string const &FileName) +{ + // write the control data to a tempfile + if (GetTempFile("deb-file-" + flNotDir(FileName), true, &Pkg) == NULL) + return false; + std::ostringstream content; + if (GetContent(content, FileName) == false) + return false; + std::string const contentstr = content.str(); + if (contentstr.empty()) + return true; + if (Pkg.Write(contentstr.c_str(), contentstr.length()) == false || Pkg.Seek(0) == false) + return false; + return true; +} +pkgCacheListParser * debDebPkgFileIndex::CreateListParser(FileFd &Pkg) +{ + if (Pkg.IsOpen() == false) + return nullptr; + _error->PushToStack(); + std::unique_ptr<pkgCacheListParser> Parser(new debDebFileParser(&Pkg, DebFile)); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + return newError ? nullptr : Parser.release(); +} +uint8_t debDebPkgFileIndex::GetIndexFlags() const +{ + return pkgCache::Flag::LocalSource; +} +std::string debDebPkgFileIndex::GetArchitecture() const +{ + return std::string(); +} +std::string debDebPkgFileIndex::GetComponent() const +{ + return "local-deb"; +} +pkgCache::PkgFileIterator debDebPkgFileIndex::FindInCache(pkgCache &Cache) const +{ + std::string const FileName = IndexFileName(); + pkgCache::PkgFileIterator File = Cache.FileBegin(); + for (; File.end() == false; ++File) + { + if (File.FileName() == NULL || FileName != File.FileName()) + continue; + // we can't do size checks here as file size != content size + return File; + } + + return File; +} +std::string debDebPkgFileIndex::ArchiveInfo_impl(pkgCache::VerIterator const &Ver) const +{ + std::string Res = IndexFileName() + " "; + Res.append(Ver.ParentPkg().Name()).append(" "); + Res.append(Ver.Arch()).append(" "); + Res.append(Ver.VerStr()); + return Res; +} + /*}}}*/ +// DscFile Index - a single .dsc file as an index /*{{{*/ +debDscFileIndex::debDscFileIndex(std::string const &DscFile) + : pkgDebianIndexRealFile(DscFile, true), d(NULL) +{ +} +pkgSrcRecords::Parser *debDscFileIndex::CreateSrcParser() const +{ + if (Exists() == false) + return NULL; + return new debDscRecordParser(File, this); +} +std::string debDscFileIndex::GetComponent() const +{ + return "local-dsc"; +} +std::string debDscFileIndex::GetArchitecture() const +{ + return "source"; +} +uint8_t debDscFileIndex::GetIndexFlags() const +{ + return pkgCache::Flag::LocalSource; +} + /*}}}*/ +// ControlFile Index - a directory with a debian/control file /*{{{*/ +std::string debDebianSourceDirIndex::GetComponent() const +{ + return "local-control"; +} + /*}}}*/ +// String Package Index - a string of Packages file content /*{{{*/ +std::string debStringPackageIndex::GetArchitecture() const +{ + return std::string(); +} +std::string debStringPackageIndex::GetComponent() const +{ + return "apt-tmp-index"; +} +uint8_t debStringPackageIndex::GetIndexFlags() const +{ + return pkgCache::Flag::NotSource; +} +const pkgIndexFile::Type *debStringPackageIndex::GetType() const +{ + return pkgIndexFile::Type::GetType("Debian Package Index"); +} +debStringPackageIndex::debStringPackageIndex(std::string const &content) : + pkgDebianIndexRealFile("", false), d(NULL) +{ + FileFd fd; + GetTempFile("apt-tmp-index", false, &fd); + fd.Write(content.data(), content.length()); + File = fd.Name(); +} +debStringPackageIndex::~debStringPackageIndex() +{ + RemoveFile("~debStringPackageIndex", File); +} + /*}}}*/ + +// Index File types for Debian /*{{{*/ +class APT_HIDDEN debIFTypeSrc : public pkgIndexFile::Type +{ + public: + debIFTypeSrc() {Label = "Debian Source Index";}; +}; +class APT_HIDDEN debIFTypePkg : public pkgIndexFile::Type +{ + public: + virtual pkgRecords::Parser *CreatePkgParser(pkgCache::PkgFileIterator const &File) const APT_OVERRIDE + { + return new debRecordParser(File.FileName(),*File.Cache()); + }; + debIFTypePkg() {Label = "Debian Package Index";}; +}; +class APT_HIDDEN debIFTypeTrans : public debIFTypePkg +{ + public: + debIFTypeTrans() {Label = "Debian Translation Index";}; +}; +class APT_HIDDEN debIFTypeStatus : public pkgIndexFile::Type +{ + public: + virtual pkgRecords::Parser *CreatePkgParser(pkgCache::PkgFileIterator const &File) const APT_OVERRIDE + { + return new debRecordParser(File.FileName(),*File.Cache()); + }; + debIFTypeStatus() {Label = "Debian dpkg status file";}; +}; +class APT_HIDDEN debIFTypeDebPkgFile : public pkgIndexFile::Type +{ + public: + virtual pkgRecords::Parser *CreatePkgParser(pkgCache::PkgFileIterator const &File) const APT_OVERRIDE + { + return new debDebFileRecordParser(File.FileName()); + }; + debIFTypeDebPkgFile() {Label = "Debian deb file";}; +}; +class APT_HIDDEN debIFTypeDscFile : public pkgIndexFile::Type +{ + public: + virtual pkgSrcRecords::Parser *CreateSrcPkgParser(std::string const &DscFile) const APT_OVERRIDE + { + return new debDscRecordParser(DscFile, NULL); + }; + debIFTypeDscFile() {Label = "Debian dsc file";}; +}; +class APT_HIDDEN debIFTypeDebianSourceDir : public pkgIndexFile::Type +{ + public: + virtual pkgSrcRecords::Parser *CreateSrcPkgParser(std::string const &SourceDir) const APT_OVERRIDE + { + return new debDscRecordParser(SourceDir + std::string("/debian/control"), NULL); + }; + debIFTypeDebianSourceDir() {Label = "Debian control file";}; +}; + +APT_HIDDEN debIFTypeSrc _apt_Src; +APT_HIDDEN debIFTypePkg _apt_Pkg; +APT_HIDDEN debIFTypeTrans _apt_Trans; +APT_HIDDEN debIFTypeStatus _apt_Status; +APT_HIDDEN debIFTypeDebPkgFile _apt_DebPkgFile; +// file based pseudo indexes +APT_HIDDEN debIFTypeDscFile _apt_DscFile; +APT_HIDDEN debIFTypeDebianSourceDir _apt_DebianSourceDir; + +const pkgIndexFile::Type *debSourcesIndex::GetType() const +{ + return &_apt_Src; +} +const pkgIndexFile::Type *debPackagesIndex::GetType() const +{ + return &_apt_Pkg; +} +const pkgIndexFile::Type *debTranslationsIndex::GetType() const +{ + return &_apt_Trans; +} +const pkgIndexFile::Type *debStatusIndex::GetType() const +{ + return &_apt_Status; +} +const pkgIndexFile::Type *debDebPkgFileIndex::GetType() const +{ + return &_apt_DebPkgFile; +} +const pkgIndexFile::Type *debDscFileIndex::GetType() const +{ + return &_apt_DscFile; +} +const pkgIndexFile::Type *debDebianSourceDirIndex::GetType() const +{ + return &_apt_DebianSourceDir; +} + /*}}}*/ + +debStatusIndex::~debStatusIndex() {} +debPackagesIndex::~debPackagesIndex() {} +debTranslationsIndex::~debTranslationsIndex() {} +debSourcesIndex::~debSourcesIndex() {} + +debDebPkgFileIndex::~debDebPkgFileIndex() {} +debDscFileIndex::~debDscFileIndex() {} diff --git a/apt-pkg/deb/debindexfile.h b/apt-pkg/deb/debindexfile.h new file mode 100644 index 0000000..5c89f9c --- /dev/null +++ b/apt-pkg/deb/debindexfile.h @@ -0,0 +1,197 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Debian Index Files + + There are three sorts currently + + Package files that have File: tags + Package files that don't (/var/lib/dpkg/status) + Source files + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_DEBINDEXFILE_H +#define PKGLIB_DEBINDEXFILE_H + +#include <apt-pkg/indexfile.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/srcrecords.h> + +#include <string> + +class OpProgress; +class pkgAcquire; +class pkgCacheGenerator; + +class debStatusIndex : public pkgDebianIndexRealFile +{ + void * const d; +protected: + virtual std::string GetArchitecture() const APT_OVERRIDE; + virtual std::string GetComponent() const APT_OVERRIDE; + virtual uint8_t GetIndexFlags() const APT_OVERRIDE; + +public: + + virtual const Type *GetType() const APT_OVERRIDE APT_PURE; + + // Interface for the Cache Generator + virtual bool HasPackages() const APT_OVERRIDE {return true;}; + // Abort if the file does not exist. + virtual bool Exists() const APT_OVERRIDE {return true;}; + + virtual pkgCacheListParser * CreateListParser(FileFd &Pkg) APT_OVERRIDE; + + debStatusIndex(std::string const &File); + virtual ~debStatusIndex(); +}; + +class debPackagesIndex : public pkgDebianIndexTargetFile +{ + void * const d; +protected: + virtual uint8_t GetIndexFlags() const APT_OVERRIDE; + +public: + virtual const Type *GetType() const APT_OVERRIDE APT_PURE; + + // Stuff for accessing files on remote items + virtual std::string ArchiveInfo(pkgCache::VerIterator const &Ver) const APT_OVERRIDE; + + // Interface for the Cache Generator + virtual bool HasPackages() const APT_OVERRIDE {return true;}; + + debPackagesIndex(IndexTarget const &Target, bool const Trusted); + virtual ~debPackagesIndex(); +}; + +class debTranslationsIndex : public pkgDebianIndexTargetFile +{ + void * const d; +protected: + virtual std::string GetArchitecture() const APT_OVERRIDE; + virtual uint8_t GetIndexFlags() const APT_OVERRIDE; + virtual bool OpenListFile(FileFd &Pkg, std::string const &FileName) APT_OVERRIDE; + APT_HIDDEN virtual pkgCacheListParser * CreateListParser(FileFd &Pkg) APT_OVERRIDE; + +public: + + virtual const Type *GetType() const APT_OVERRIDE APT_PURE; + + // Interface for the Cache Generator + virtual bool HasPackages() const APT_OVERRIDE; + + debTranslationsIndex(IndexTarget const &Target); + virtual ~debTranslationsIndex(); +}; + +class debSourcesIndex : public pkgDebianIndexTargetFile +{ + void * const d; + virtual uint8_t GetIndexFlags() const APT_OVERRIDE; + virtual bool OpenListFile(FileFd &Pkg, std::string const &FileName) APT_OVERRIDE; + APT_HIDDEN virtual pkgCacheListParser * CreateListParser(FileFd &Pkg) APT_OVERRIDE; + + public: + + virtual const Type *GetType() const APT_OVERRIDE APT_PURE; + + // Stuff for accessing files on remote items + virtual std::string SourceInfo(pkgSrcRecords::Parser const &Record, + pkgSrcRecords::File const &File) const APT_OVERRIDE; + + // Interface for the record parsers + virtual pkgSrcRecords::Parser *CreateSrcParser() const APT_OVERRIDE; + + // Interface for the Cache Generator + virtual bool HasPackages() const APT_OVERRIDE {return false;}; + + debSourcesIndex(IndexTarget const &Target, bool const Trusted); + virtual ~debSourcesIndex(); +}; + +class debDebPkgFileIndex : public pkgDebianIndexRealFile +{ + void * const d; + std::string DebFile; + +protected: + virtual std::string GetComponent() const APT_OVERRIDE; + virtual std::string GetArchitecture() const APT_OVERRIDE; + virtual uint8_t GetIndexFlags() const APT_OVERRIDE; + virtual bool OpenListFile(FileFd &Pkg, std::string const &FileName) APT_OVERRIDE; + APT_HIDDEN virtual pkgCacheListParser * CreateListParser(FileFd &Pkg) APT_OVERRIDE; + +public: + virtual const Type *GetType() const APT_OVERRIDE APT_PURE; + + /** get the control (file) content of the deb file + * + * @param[out] content of the control file + * @param debfile is the filename of the .deb-file + * @return \b true if successful, otherwise \b false. + */ + static bool GetContent(std::ostream &content, std::string const &debfile); + + // Interface for the Cache Generator + virtual bool HasPackages() const APT_OVERRIDE {return true;} + virtual pkgCache::PkgFileIterator FindInCache(pkgCache &Cache) const APT_OVERRIDE; + + // Interface for acquire + + debDebPkgFileIndex(std::string const &DebFile); + virtual ~debDebPkgFileIndex(); + + //FIXME: use proper virtual-handling on next ABI break + APT_HIDDEN std::string ArchiveInfo_impl(pkgCache::VerIterator const &Ver) const; +}; + +class debDscFileIndex : public pkgDebianIndexRealFile +{ + void * const d; + +protected: + virtual std::string GetComponent() const APT_OVERRIDE; + virtual std::string GetArchitecture() const APT_OVERRIDE; + virtual uint8_t GetIndexFlags() const APT_OVERRIDE; + +public: + virtual const Type *GetType() const APT_OVERRIDE APT_PURE; + virtual pkgSrcRecords::Parser *CreateSrcParser() const APT_OVERRIDE; + virtual bool HasPackages() const APT_OVERRIDE {return false;}; + + debDscFileIndex(std::string const &DscFile); + virtual ~debDscFileIndex(); +}; + +class debDebianSourceDirIndex : public debDscFileIndex +{ +protected: + virtual std::string GetComponent() const APT_OVERRIDE; + +public: + virtual const Type *GetType() const APT_OVERRIDE APT_PURE; +}; + +class debStringPackageIndex : public pkgDebianIndexRealFile +{ + void * const d; +protected: + virtual std::string GetArchitecture() const APT_OVERRIDE; + virtual std::string GetComponent() const APT_OVERRIDE; + virtual uint8_t GetIndexFlags() const APT_OVERRIDE; + +public: + virtual const Type *GetType() const APT_OVERRIDE APT_PURE; + + // Interface for the Cache Generator + virtual bool HasPackages() const APT_OVERRIDE {return true;}; + // Abort if the file does not exist. + virtual bool Exists() const APT_OVERRIDE {return true;}; + + debStringPackageIndex(std::string const &content); + virtual ~debStringPackageIndex(); +}; +#endif diff --git a/apt-pkg/deb/deblistparser.cc b/apt-pkg/deb/deblistparser.cc new file mode 100644 index 0000000..80ca10e --- /dev/null +++ b/apt-pkg/deb/deblistparser.cc @@ -0,0 +1,1066 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Package Cache Generator - Generator for the cache structure. + + This builds the cache structure from the abstract package list parser. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/cachefilter.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/crc-16.h> +#include <apt-pkg/deblistparser.h> +#include <apt-pkg/error.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/md5.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile-keys.h> +#include <apt-pkg/tagfile.h> + +#include <algorithm> +#include <string> +#include <vector> +#include <ctype.h> +#include <stddef.h> +#include <string.h> + /*}}}*/ + +using std::string; +using APT::StringView; + +static const debListParser::WordList PrioList[] = { + {"required",pkgCache::State::Required}, + {"important",pkgCache::State::Important}, + {"standard",pkgCache::State::Standard}, + {"optional",pkgCache::State::Optional}, + {"extra",pkgCache::State::Extra}, + {"", 0}}; + +// ListParser::debListParser - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* Provide an architecture and only this one and "all" will be accepted + in Step(), if no Architecture is given we will accept every arch + we would accept in general with checkArchitecture() */ +debListParser::debListParser(FileFd *File) : + pkgCacheListParser(), Tags(File) +{ + // this dance allows an empty value to override the default + if (_config->Exists("pkgCacheGen::ForceEssential")) + { + forceEssential = _config->FindVector("pkgCacheGen::ForceEssential"); + if (forceEssential.empty() == false && _config->Find("pkgCacheGen::ForceEssential").empty()) + forceEssential.emplace_back("apt"); + } + else + forceEssential.emplace_back("apt"); + forceImportant = _config->FindVector("pkgCacheGen::ForceImportant"); + myArch = _config->Find("APT::Architecture"); +} + /*}}}*/ +// ListParser::Package - Return the package name /*{{{*/ +// --------------------------------------------------------------------- +/* This is to return the name of the package this section describes */ +string debListParser::Package() { + string Result = Section.Find(pkgTagSection::Key::Package).to_string(); + + // Normalize mixed case package names to lower case, like dpkg does + // See Bug#807012 for details. + // Only do this when the package name does not contain a / - as that + // indicates that the package name was derived from a filename given + // to install or build-dep or similar (Bug#854794) + if (likely(Result.find('/') == string::npos)) + { + for (char &c: Result) + { + char l = tolower_ascii_inline(c); + if (unlikely(l != c)) + c = l; + } + } + + if(unlikely(Result.empty() == true)) + _error->Error("Encountered a section with no Package: header"); + return Result; +} + /*}}}*/ +// ListParser::Architecture - Return the package arch /*{{{*/ +// --------------------------------------------------------------------- +/* This will return the Architecture of the package this section describes */ +APT::StringView debListParser::Architecture() { + auto const Arch = Section.Find(pkgTagSection::Key::Architecture); + return Arch.empty() ? "none" : Arch; +} + /*}}}*/ +// ListParser::ArchitectureAll /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool debListParser::ArchitectureAll() { + return Section.Find(pkgTagSection::Key::Architecture) == "all"; +} + /*}}}*/ +// ListParser::Version - Return the version string /*{{{*/ +// --------------------------------------------------------------------- +/* This is to return the string describing the version in debian form, + epoch:upstream-release. If this returns the blank string then the + entry is assumed to only describe package properties */ +APT::StringView debListParser::Version() +{ + return Section.Find(pkgTagSection::Key::Version); +} + /*}}}*/ +unsigned char debListParser::ParseMultiArch(bool const showErrors) /*{{{*/ +{ + unsigned char MA; + auto const MultiArch = Section.Find(pkgTagSection::Key::Multi_Arch); + if (MultiArch.empty() == true || MultiArch == "no") + MA = pkgCache::Version::No; + else if (MultiArch == "same") { + if (ArchitectureAll() == true) + { + if (showErrors == true) + _error->Warning("Architecture: all package '%s' can't be Multi-Arch: same", + Section.FindS("Package").c_str()); + MA = pkgCache::Version::No; + } + else + MA = pkgCache::Version::Same; + } + else if (MultiArch == "foreign") + MA = pkgCache::Version::Foreign; + else if (MultiArch == "allowed") + MA = pkgCache::Version::Allowed; + else + { + if (showErrors == true) + _error->Warning("Unknown Multi-Arch type '%s' for package '%s'", + MultiArch.to_string().c_str(), Section.FindS("Package").c_str()); + MA = pkgCache::Version::No; + } + + if (ArchitectureAll() == true) + MA |= pkgCache::Version::All; + + return MA; +} + /*}}}*/ +// ListParser::NewVersion - Fill in the version structure /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool debListParser::NewVersion(pkgCache::VerIterator &Ver) +{ + const char *Start; + const char *Stop; + + // Parse the section + if (Section.Find(pkgTagSection::Key::Section,Start,Stop) == true) + { + map_stringitem_t const idx = StoreString(pkgCacheGenerator::SECTION, Start, Stop - Start); + Ver->Section = idx; + } + // Parse the source package name + pkgCache::GrpIterator G = Ver.ParentPkg().Group(); + Ver->SourcePkgName = G->Name; + Ver->SourceVerStr = Ver->VerStr; + if (Section.Find(pkgTagSection::Key::Source,Start,Stop) == true) + { + const char * const Space = static_cast<const char *>(memchr(Start, ' ', Stop - Start)); + pkgCache::VerIterator V; + + if (Space != NULL) + { + const char * const Open = static_cast<const char *>(memchr(Space, '(', Stop - Space)); + if (likely(Open != NULL)) + { + const char * const Close = static_cast<const char *>(memchr(Open, ')', Stop - Open)); + if (likely(Close != NULL)) + { + APT::StringView const version(Open + 1, (Close - Open) - 1); + if (version != Ver.VerStr()) + { + map_stringitem_t const idx = StoreString(pkgCacheGenerator::VERSIONNUMBER, version); + G = Ver.ParentPkg().Group(); + Ver->SourceVerStr = idx; + } + } + } + Stop = Space; + } + + APT::StringView const pkgname(Start, Stop - Start); + if (pkgname != G.Name()) + { + for (pkgCache::PkgIterator P = G.PackageList(); P.end() == false; P = G.NextPkg(P)) + { + for (V = P.VersionList(); V.end() == false; ++V) + { + if (pkgname == V.SourcePkgName()) + { + Ver->SourcePkgName = V->SourcePkgName; + break; + } + } + if (V.end() == false) + break; + } + if (V.end() == true) + { + map_stringitem_t const idx = StoreString(pkgCacheGenerator::PKGNAME, pkgname); + G = Ver.ParentPkg().Group(); + Ver->SourcePkgName = idx; + } + } + } + + Ver->MultiArch = ParseMultiArch(true); + // Archive Size + Ver->Size = Section.FindULL(pkgTagSection::Key::Size); + // Unpacked Size (in K) + Ver->InstalledSize = Section.FindULL(pkgTagSection::Key::Installed_Size); + Ver->InstalledSize *= 1024; + + // Priority + if (Section.Find(pkgTagSection::Key::Priority,Start,Stop) == true) + { + if (GrabWord(StringView(Start,Stop-Start),PrioList,Ver->Priority) == false) + Ver->Priority = pkgCache::State::Extra; + } + + if (ParseDepends(Ver,pkgTagSection::Key::Pre_Depends,pkgCache::Dep::PreDepends) == false) + return false; + if (ParseDepends(Ver,pkgTagSection::Key::Depends,pkgCache::Dep::Depends) == false) + return false; + if (ParseDepends(Ver,pkgTagSection::Key::Conflicts,pkgCache::Dep::Conflicts) == false) + return false; + if (ParseDepends(Ver,pkgTagSection::Key::Breaks,pkgCache::Dep::DpkgBreaks) == false) + return false; + if (ParseDepends(Ver,pkgTagSection::Key::Recommends,pkgCache::Dep::Recommends) == false) + return false; + if (ParseDepends(Ver,pkgTagSection::Key::Suggests,pkgCache::Dep::Suggests) == false) + return false; + if (ParseDepends(Ver,pkgTagSection::Key::Replaces,pkgCache::Dep::Replaces) == false) + return false; + if (ParseDepends(Ver,pkgTagSection::Key::Enhances,pkgCache::Dep::Enhances) == false) + return false; + // Obsolete. + if (ParseDepends(Ver,pkgTagSection::Key::Optional,pkgCache::Dep::Suggests) == false) + return false; + + if (ParseProvides(Ver) == false) + return false; + + return true; +} + /*}}}*/ +// ListParser::AvailableDescriptionLanguages /*{{{*/ +std::vector<std::string> debListParser::AvailableDescriptionLanguages() +{ + std::vector<std::string> const understood = APT::Configuration::getLanguages(); + std::vector<std::string> avail; + static constexpr int prefixLen = 12; + char buf[32] = "Description-"; + if (Section.Exists("Description") == true) + avail.push_back(""); + for (std::vector<std::string>::const_iterator lang = understood.begin(); lang != understood.end(); ++lang) + { + if (unlikely(lang->size() > sizeof(buf) - prefixLen)) { + _error->Warning("Ignoring translated description %s", lang->c_str()); + continue; + } + memcpy(buf + prefixLen, lang->c_str(), lang->size()); + if (Section.Exists(StringView(buf, prefixLen + lang->size())) == true) + avail.push_back(*lang); + } + return avail; +} + /*}}}*/ +// ListParser::Description_md5 - Return the description_md5 MD5SumValue /*{{{*/ +// --------------------------------------------------------------------- +/* This is to return the md5 string to allow the check if it is the right + description. If no Description-md5 is found in the section it will be + calculated. + */ +APT::StringView debListParser::Description_md5() +{ + StringView const value = Section.Find(pkgTagSection::Key::Description_md5); + if (unlikely(value.empty() == true)) + { + StringView const desc = Section.Find(pkgTagSection::Key::Description); + if (desc == "\n") + return StringView(); + + MD5Summation md5; + md5.Add(desc.data(), desc.size()); + md5.Add("\n"); + MD5Buffer = md5.Result(); + return StringView(MD5Buffer); + } + else if (likely(value.size() == 32)) + { + return value; + } + _error->Error("Malformed Description-md5 line; doesn't have the required length (32 != %d) '%.*s'", (int)value.size(), (int)value.length(), value.data()); + return StringView(); +} + /*}}}*/ +// ListParser::UsePackage - Update a package structure /*{{{*/ +// --------------------------------------------------------------------- +/* This is called to update the package with any new information + that might be found in the section */ +bool debListParser::UsePackage(pkgCache::PkgIterator &Pkg, + pkgCache::VerIterator &Ver) +{ + string const static myArch = _config->Find("APT::Architecture"); + // Possible values are: "all", "native", "installed" and "none" + // The "installed" mode is handled by ParseStatus(), See #544481 and friends. + string const static essential = _config->Find("pkgCacheGen::Essential", "all"); + if (essential == "all" || + (essential == "native" && Pkg->Arch != 0 && myArch == Pkg.Arch())) + if (Section.FindFlag(pkgTagSection::Key::Essential,Pkg->Flags,pkgCache::Flag::Essential) == false) + return false; + if (Section.FindFlag(pkgTagSection::Key::Important,Pkg->Flags,pkgCache::Flag::Important) == false) + return false; + + if (std::find(forceEssential.begin(), forceEssential.end(), Pkg.Name()) != forceEssential.end()) + { + if ((essential == "native" && Pkg->Arch != 0 && myArch == Pkg.Arch()) || + essential == "all") + Pkg->Flags |= pkgCache::Flag::Essential | pkgCache::Flag::Important; + else + Pkg->Flags |= pkgCache::Flag::Important; + } + else if (std::find(forceImportant.begin(), forceImportant.end(), Pkg.Name()) != forceImportant.end()) + Pkg->Flags |= pkgCache::Flag::Important; + + if (ParseStatus(Pkg,Ver) == false) + return false; + return true; +} + /*}}}*/ +// ListParser::VersionHash - Compute a unique hash for this version /*{{{*/ +// --------------------------------------------------------------------- +/* */ +unsigned short debListParser::VersionHash() +{ + static constexpr pkgTagSection::Key Sections[] ={ + pkgTagSection::Key::Installed_Size, + pkgTagSection::Key::Depends, + pkgTagSection::Key::Pre_Depends, +// pkgTagSection::Key::Suggests, +// pkgTagSection::Key::Recommends", + pkgTagSection::Key::Conflicts, + pkgTagSection::Key::Breaks, + pkgTagSection::Key::Replaces}; + unsigned long Result = INIT_FCS; + for (auto I : Sections) + { + const char *Start; + const char *End; + if (Section.Find(I,Start,End) == false) + continue; + + /* Strip out any spaces from the text, this undoes dpkgs reformatting + of certain fields. dpkg also has the rather interesting notion of + reformatting depends operators < -> <=, so we drop all = from the + string to make that not matter. */ + for (; Start != End; ++Start) + { + // Strip away 0: epochs from input + if (*Start == '0' && Start[1] == ':') { + Start++; // Skip the : + continue; // Skip the 0 + } + if (isspace_ascii(*Start) != 0 || *Start == '=') + continue; + Result = AddCRC16Byte(Result, tolower_ascii_unsafe(*Start)); + } + + + } + + return Result; +} + /*}}}*/ +// StatusListParser::ParseStatus - Parse the status field /*{{{*/ +// --------------------------------------------------------------------- +/* Status lines are of the form, + Status: want flag status + want = unknown, install, hold, deinstall, purge + flag = ok, reinstreq + status = not-installed, config-files, half-installed, unpacked, + half-configured, triggers-awaited, triggers-pending, installed + */ +bool debListParser::ParseStatus(pkgCache::PkgIterator &, + pkgCache::VerIterator &) +{ + return true; +} +bool debStatusListParser::ParseStatus(pkgCache::PkgIterator &Pkg, + pkgCache::VerIterator &Ver) +{ + const char *Start; + const char *Stop; + if (Section.Find(pkgTagSection::Key::Status,Start,Stop) == false) + return true; + + // UsePackage() is responsible for setting the flag in the default case + bool const static essential = _config->Find("pkgCacheGen::Essential", "") == "installed"; + if (essential == true && + Section.FindFlag(pkgTagSection::Key::Essential,Pkg->Flags,pkgCache::Flag::Essential) == false) + return false; + + // Isolate the first word + const char *I = Start; + for(; I < Stop && *I != ' '; I++); + if (I >= Stop || *I != ' ') + return _error->Error("Malformed Status line"); + + // Process the want field + WordList WantList[] = {{"unknown",pkgCache::State::Unknown}, + {"install",pkgCache::State::Install}, + {"hold",pkgCache::State::Hold}, + {"deinstall",pkgCache::State::DeInstall}, + {"purge",pkgCache::State::Purge}, + {"", 0}}; + if (GrabWord(StringView(Start,I-Start),WantList,Pkg->SelectedState) == false) + return _error->Error("Malformed 1st word in the Status line"); + + // Isloate the next word + I++; + Start = I; + for(; I < Stop && *I != ' '; I++); + if (I >= Stop || *I != ' ') + return _error->Error("Malformed status line, no 2nd word"); + + // Process the flag field + WordList FlagList[] = {{"ok",pkgCache::State::Ok}, + {"reinstreq",pkgCache::State::ReInstReq}, + {"hold",pkgCache::State::HoldInst}, + {"hold-reinstreq",pkgCache::State::HoldReInstReq}, + {"", 0}}; + if (GrabWord(StringView(Start,I-Start),FlagList,Pkg->InstState) == false) + return _error->Error("Malformed 2nd word in the Status line"); + + // Isloate the last word + I++; + Start = I; + for(; I < Stop && *I != ' '; I++); + if (I != Stop) + return _error->Error("Malformed Status line, no 3rd word"); + + // Process the flag field + WordList StatusList[] = {{"not-installed",pkgCache::State::NotInstalled}, + {"config-files",pkgCache::State::ConfigFiles}, + {"half-installed",pkgCache::State::HalfInstalled}, + {"unpacked",pkgCache::State::UnPacked}, + {"half-configured",pkgCache::State::HalfConfigured}, + {"triggers-awaited",pkgCache::State::TriggersAwaited}, + {"triggers-pending",pkgCache::State::TriggersPending}, + {"installed",pkgCache::State::Installed}, + {"", 0}}; + if (GrabWord(StringView(Start,I-Start),StatusList,Pkg->CurrentState) == false) + return _error->Error("Malformed 3rd word in the Status line"); + + /* A Status line marks the package as indicating the current + version as well. Only if it is actually installed.. Otherwise + the interesting dpkg handling of the status file creates bogus + entries. */ + if (!(Pkg->CurrentState == pkgCache::State::NotInstalled || + Pkg->CurrentState == pkgCache::State::ConfigFiles)) + { + if (Ver.end() == true) + _error->Warning("Encountered status field in a non-version description"); + else + Pkg->CurrentVer = Ver.Index(); + } + + return true; +} + +const char *debListParser::ConvertRelation(const char *I,unsigned int &Op) +{ + // Determine the operator + switch (*I) + { + case '<': + I++; + if (*I == '=') + { + I++; + Op = pkgCache::Dep::LessEq; + break; + } + + if (*I == '<') + { + I++; + Op = pkgCache::Dep::Less; + break; + } + + // < is the same as <= and << is really Cs < for some reason + Op = pkgCache::Dep::LessEq; + break; + + case '>': + I++; + if (*I == '=') + { + I++; + Op = pkgCache::Dep::GreaterEq; + break; + } + + if (*I == '>') + { + I++; + Op = pkgCache::Dep::Greater; + break; + } + + // > is the same as >= and >> is really Cs > for some reason + Op = pkgCache::Dep::GreaterEq; + break; + + case '=': + Op = pkgCache::Dep::Equals; + I++; + break; + + // HACK around bad package definitions + default: + Op = pkgCache::Dep::Equals; + break; + } + return I; +} + /*}}}*/ +// ListParser::ParseDepends - Parse a dependency element /*{{{*/ +// --------------------------------------------------------------------- +/* This parses the dependency elements out of a standard string in place, + bit by bit. */ +const char *debListParser::ParseDepends(const char *Start,const char *Stop, + std::string &Package,std::string &Ver,unsigned int &Op) + { return ParseDepends(Start, Stop, Package, Ver, Op, false, true, false); } +const char *debListParser::ParseDepends(const char *Start,const char *Stop, + std::string &Package,std::string &Ver,unsigned int &Op, + bool const &ParseArchFlags) + { return ParseDepends(Start, Stop, Package, Ver, Op, ParseArchFlags, true, false); } +const char *debListParser::ParseDepends(const char *Start,const char *Stop, + std::string &Package,std::string &Ver,unsigned int &Op, + bool const &ParseArchFlags, bool const &StripMultiArch) + { return ParseDepends(Start, Stop, Package, Ver, Op, ParseArchFlags, StripMultiArch, false); } +const char *debListParser::ParseDepends(const char *Start,const char *Stop, + string &Package,string &Ver, + unsigned int &Op, bool const &ParseArchFlags, + bool const &StripMultiArch, + bool const &ParseRestrictionsList) +{ + return debListParser::ParseDepends(Start, Stop, Package, Ver, Op, ParseArchFlags, + StripMultiArch, ParseRestrictionsList, + _config->Find("APT::Architecture")); +} + +const char *debListParser::ParseDepends(const char *Start,const char *Stop, + string &Package,string &Ver, + unsigned int &Op, bool const &ParseArchFlags, + bool const &StripMultiArch, + bool const &ParseRestrictionsList, + string const &Arch) +{ + StringView PackageView; + StringView VerView; + + auto res = ParseDepends(Start, Stop, PackageView, VerView, Op, (bool)ParseArchFlags, + (bool) StripMultiArch, (bool) ParseRestrictionsList, Arch); + Package = PackageView.to_string(); + Ver = VerView.to_string(); + + return res; +} +const char *debListParser::ParseDepends(const char *Start,const char *Stop, + StringView &Package,StringView &Ver, + unsigned int &Op, bool ParseArchFlags, + bool StripMultiArch, + bool ParseRestrictionsList) +{ + return debListParser::ParseDepends(Start, Stop, Package, Ver, Op, ParseArchFlags, + StripMultiArch, ParseRestrictionsList, + _config->Find("APT::Architecture")); +} + +const char *debListParser::ParseDepends(const char *Start,const char *Stop, + StringView &Package,StringView &Ver, + unsigned int &Op, bool ParseArchFlags, + bool StripMultiArch, + bool ParseRestrictionsList, string const &Arch) +{ + // Strip off leading space + for (;Start != Stop && isspace_ascii(*Start) != 0; ++Start); + + // Parse off the package name + const char *I = Start; + for (;I != Stop && isspace_ascii(*I) == 0 && *I != '(' && *I != ')' && + *I != ',' && *I != '|' && *I != '[' && *I != ']' && + *I != '<' && *I != '>'; ++I); + + // Malformed, no '(' + if (I != Stop && *I == ')') + return 0; + + if (I == Start) + return 0; + + // Stash the package name + Package = StringView(Start, I - Start); + + // We don't want to confuse library users which can't handle MultiArch + if (StripMultiArch == true) { + size_t const found = Package.rfind(':'); + if (found != StringView::npos && + (Package.substr(found) == ":any" || + Package.substr(found) == ":native" || + Package.substr(found +1) == Arch)) + Package = Package.substr(0,found); + } + + // Skip white space to the '(' + for (;I != Stop && isspace_ascii(*I) != 0 ; I++); + + // Parse a version + if (I != Stop && *I == '(') + { + // Skip the '(' + for (I++; I != Stop && isspace_ascii(*I) != 0 ; I++); + if (I + 3 >= Stop) + return 0; + I = ConvertRelation(I,Op); + + // Skip whitespace + for (;I != Stop && isspace_ascii(*I) != 0; I++); + Start = I; + I = (const char*) memchr(I, ')', Stop - I); + if (I == NULL || Start == I) + return 0; + + // Skip trailing whitespace + const char *End = I; + for (; End > Start && isspace_ascii(End[-1]); End--); + + Ver = StringView(Start,End-Start); + I++; + } + else + { + Ver = StringView(); + Op = pkgCache::Dep::NoOp; + } + + // Skip whitespace + for (;I != Stop && isspace_ascii(*I) != 0; I++); + + if (unlikely(ParseArchFlags == true)) + { + APT::CacheFilter::PackageArchitectureMatchesSpecification matchesArch(Arch, false); + + // Parse an architecture + if (I != Stop && *I == '[') + { + ++I; + // malformed + if (unlikely(I == Stop)) + return 0; + + const char *End = I; + bool Found = false; + bool NegArch = false; + while (I != Stop) + { + // look for whitespace or ending ']' + for (;End != Stop && !isspace_ascii(*End) && *End != ']'; ++End); + + if (unlikely(End == Stop)) + return 0; + + if (*I == '!') + { + NegArch = true; + ++I; + } + + std::string const arch(I, End); + if (arch.empty() == false && matchesArch(arch.c_str()) == true) + { + Found = true; + if (I[-1] != '!') + NegArch = false; + // we found a match, so fast-forward to the end of the wildcards + for (; End != Stop && *End != ']'; ++End); + } + + if (*End++ == ']') { + I = End; + break; + } + + I = End; + for (;I != Stop && isspace_ascii(*I) != 0; I++); + } + + if (NegArch == true) + Found = !Found; + + if (Found == false) + Package = ""; /* not for this arch */ + } + + // Skip whitespace + for (;I != Stop && isspace_ascii(*I) != 0; I++); + } + + if (unlikely(ParseRestrictionsList == true)) + { + // Parse a restrictions formula which is in disjunctive normal form: + // (foo AND bar) OR (blub AND bla) + + std::vector<string> const profiles = APT::Configuration::getBuildProfiles(); + + // if the next character is a restriction list, then by default the + // dependency does not apply and the conditions have to be checked + // if the next character is not a restriction list, then by default the + // dependency applies + bool applies1 = (*I != '<'); + while (I != Stop) + { + if (*I != '<') + break; + + ++I; + // malformed + if (unlikely(I == Stop)) + return 0; + + const char *End = I; + + // if of the prior restriction list is already fulfilled, then + // we can just skip to the end of the current list + if (applies1) { + for (;End != Stop && *End != '>'; ++End); + I = ++End; + // skip whitespace + for (;I != Stop && isspace_ascii(*I) != 0; I++); + } else { + bool applies2 = true; + // all the conditions inside a restriction list have to be + // met so once we find one that is not met, we can skip to + // the end of this list + while (I != Stop) + { + // look for whitespace or ending '>' + // End now points to the character after the current term + for (;End != Stop && !isspace_ascii(*End) && *End != '>'; ++End); + + if (unlikely(End == Stop)) + return 0; + + bool NegRestriction = false; + if (*I == '!') + { + NegRestriction = true; + ++I; + } + + std::string const restriction(I, End); + if (restriction.empty() == false && profiles.empty() == false && + std::find(profiles.begin(), profiles.end(), restriction) != profiles.end()) + { + if (NegRestriction) { + applies2 = false; + // since one of the terms does not apply we don't have to check the others + for (; End != Stop && *End != '>'; ++End); + } + } else { + if (!NegRestriction) { + applies2 = false; + // since one of the terms does not apply we don't have to check the others + for (; End != Stop && *End != '>'; ++End); + } + } + + if (*End++ == '>') { + I = End; + // skip whitespace + for (;I != Stop && isspace_ascii(*I) != 0; I++); + break; + } + + I = End; + // skip whitespace + for (;I != Stop && isspace_ascii(*I) != 0; I++); + } + if (applies2) { + applies1 = true; + } + } + } + + if (applies1 == false) { + Package = ""; //not for this restriction + } + } + + if (I != Stop && *I == '|') + Op |= pkgCache::Dep::Or; + + if (I == Stop || *I == ',' || *I == '|') + { + if (I != Stop) + for (I++; I != Stop && isspace_ascii(*I) != 0; I++); + return I; + } + + return 0; +} + /*}}}*/ +// ListParser::ParseDepends - Parse a dependency list /*{{{*/ +// --------------------------------------------------------------------- +/* This is the higher level depends parser. It takes a tag and generates + a complete depends tree for the given version. */ +bool debListParser::ParseDepends(pkgCache::VerIterator &Ver, + pkgTagSection::Key Key,unsigned int Type) +{ + const char *Start; + const char *Stop; + if (Section.Find(Key,Start,Stop) == false || Start == Stop) + return true; + + string const pkgArch = Ver.Arch(); + + while (1) + { + StringView Package; + StringView Version; + unsigned int Op; + + Start = ParseDepends(Start, Stop, Package, Version, Op, false, false, false, myArch); + if (Start == 0) + return _error->Error("Problem parsing dependency %zu",static_cast<size_t>(Key)); // TODO + size_t const found = Package.rfind(':'); + + if (found == string::npos) + { + if (NewDepends(Ver,Package,pkgArch,Version,Op,Type) == false) + return false; + } + else if (Package.substr(found) == ":any") + { + if (NewDepends(Ver,Package,"any",Version,Op,Type) == false) + return false; + } + else + { + // Such dependencies are not supposed to be accepted … + // … but this is probably the best thing to do anyway + if (Package.substr(found + 1) == "native") + { + std::string const Pkg = Package.substr(0, found).to_string() + ':' + Ver.Cache()->NativeArch(); + if (NewDepends(Ver, Pkg, "any", Version, Op | pkgCache::Dep::ArchSpecific, Type) == false) + return false; + } + else if (NewDepends(Ver, Package, "any", Version, Op | pkgCache::Dep::ArchSpecific, Type) == false) + return false; + } + + if (Start == Stop) + break; + } + return true; +} + /*}}}*/ +// ListParser::ParseProvides - Parse the provides list /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool debListParser::ParseProvides(pkgCache::VerIterator &Ver) +{ + /* it is unlikely, but while parsing dependencies, we might have already + picked up multi-arch implicit provides which we do not want to duplicate here */ + bool hasProvidesAlready = false; + std::string const spzName = Ver.ParentPkg().FullName(false); + { + for (pkgCache::PrvIterator Prv = Ver.ProvidesList(); Prv.end() == false; ++Prv) + { + if (Prv.IsMultiArchImplicit() == false || (Prv->Flags & pkgCache::Flag::ArchSpecific) == 0) + continue; + if (spzName != Prv.OwnerPkg().FullName(false)) + continue; + hasProvidesAlready = true; + break; + } + } + + string const Arch = Ver.Arch(); + const char *Start; + const char *Stop; + if (Section.Find(pkgTagSection::Key::Provides,Start,Stop) == true) + { + StringView Package; + StringView Version; + unsigned int Op; + + do + { + Start = ParseDepends(Start,Stop,Package,Version,Op, false, false, false); + const size_t archfound = Package.rfind(':'); + if (Start == 0) + return _error->Error("Problem parsing Provides line"); + if (unlikely(Op != pkgCache::Dep::NoOp && Op != pkgCache::Dep::Equals)) { + _error->Warning("Ignoring Provides line with non-equal DepCompareOp for package %s", Package.to_string().c_str()); + } else if (archfound != string::npos) { + StringView spzArch = Package.substr(archfound + 1); + if (spzArch != "any") + { + if (NewProvides(Ver, Package.substr(0, archfound), spzArch, Version, pkgCache::Flag::MultiArchImplicit | pkgCache::Flag::ArchSpecific) == false) + return false; + } + if (NewProvides(Ver, Package, "any", Version, pkgCache::Flag::ArchSpecific) == false) + return false; + } else if ((Ver->MultiArch & pkgCache::Version::Foreign) == pkgCache::Version::Foreign) { + if (APT::Configuration::checkArchitecture(Arch)) + { + if (NewProvidesAllArch(Ver, Package, Version, 0) == false) + return false; + } + else if (NewProvides(Ver, Package, Arch, Version, 0) == false) + return false; + } else { + if ((Ver->MultiArch & pkgCache::Version::Allowed) == pkgCache::Version::Allowed) + { + if (NewProvides(Ver, Package.to_string().append(":any"), "any", Version, pkgCache::Flag::MultiArchImplicit) == false) + return false; + } + if (NewProvides(Ver, Package, Arch, Version, 0) == false) + return false; + } + if (archfound == std::string::npos) + { + string spzName = Package.to_string(); + spzName.push_back(':'); + spzName.append(Ver.ParentPkg().Arch()); + pkgCache::PkgIterator const spzPkg = Ver.Cache()->FindPkg(spzName, "any"); + if (spzPkg.end() == false) + { + if (NewProvides(Ver, spzName, "any", Version, pkgCache::Flag::MultiArchImplicit | pkgCache::Flag::ArchSpecific) == false) + return false; + } + } + } while (Start != Stop); + } + + if (APT::Configuration::checkArchitecture(Arch)) + { + if ((Ver->MultiArch & pkgCache::Version::Allowed) == pkgCache::Version::Allowed) + { + string const Package = string(Ver.ParentPkg().Name()).append(":").append("any"); + if (NewProvides(Ver, Package, "any", Ver.VerStr(), pkgCache::Flag::MultiArchImplicit) == false) + return false; + } + else if ((Ver->MultiArch & pkgCache::Version::Foreign) == pkgCache::Version::Foreign) + { + if (NewProvidesAllArch(Ver, Ver.ParentPkg().Name(), Ver.VerStr(), pkgCache::Flag::MultiArchImplicit) == false) + return false; + } + } + + if (hasProvidesAlready == false) + { + pkgCache::PkgIterator const spzPkg = Ver.Cache()->FindPkg(spzName, "any"); + if (spzPkg.end() == false) + { + if (NewProvides(Ver, spzName, "any", Ver.VerStr(), pkgCache::Flag::MultiArchImplicit | pkgCache::Flag::ArchSpecific) == false) + return false; + } + } + return true; +} + /*}}}*/ +// ListParser::GrabWord - Matches a word and returns /*{{{*/ +// --------------------------------------------------------------------- +/* Looks for a word in a list of words - for ParseStatus */ +bool debListParser::GrabWord(StringView Word, WordList const *List, unsigned char &Out) +{ + for (unsigned int C = 0; List[C].Str.empty() == false; C++) + { + if (Word.length() == List[C].Str.length() && + strncasecmp(Word.data(), List[C].Str.data(), Word.length()) == 0) + { + Out = List[C].Val; + return true; + } + } + return false; +} + /*}}}*/ +// ListParser::Step - Move to the next section in the file /*{{{*/ +// --------------------------------------------------------------------- +/* This has to be careful to only process the correct architecture */ +bool debListParser::Step() +{ + iOffset = Tags.Offset(); + return Tags.Step(Section); +} + /*}}}*/ +// ListParser::GetPrio - Convert the priority from a string /*{{{*/ +// --------------------------------------------------------------------- +/* */ +unsigned char debListParser::GetPrio(string Str) +{ + unsigned char Out; + if (GrabWord(Str,PrioList,Out) == false) + Out = pkgCache::State::Extra; + + return Out; +} + /*}}}*/ +bool debListParser::SameVersion(unsigned short const Hash, /*{{{*/ + pkgCache::VerIterator const &Ver) +{ + if (pkgCacheListParser::SameVersion(Hash, Ver) == false) + return false; + // status file has no (Download)Size, but all others are fair game + // status file is parsed last, so the first version we encounter is + // probably also the version we have downloaded + unsigned long long const Size = Section.FindULL(pkgTagSection::Key::Size); + if (Size != 0 && Ver->Size != 0 && Size != Ver->Size) + return false; + // available everywhere, but easier to check here than to include in VersionHash + unsigned char MultiArch = ParseMultiArch(false); + if (MultiArch != Ver->MultiArch) + return false; + // for all practical proposes (we can check): same version + return true; +} + /*}}}*/ + +debDebFileParser::debDebFileParser(FileFd *File, std::string const &DebFile) + : debListParser(File), DebFile(DebFile) +{ +} + +bool debDebFileParser::UsePackage(pkgCache::PkgIterator &Pkg, + pkgCache::VerIterator &Ver) +{ + bool res = debListParser::UsePackage(Pkg, Ver); + // we use the full file path as a provides so that the file is found + // by its name + if(NewProvides(Ver, DebFile, Pkg.Cache()->NativeArch(), Ver.VerStr(), 0) == false) + return false; + return res; +} + +debListParser::~debListParser() {} diff --git a/apt-pkg/deb/deblistparser.h b/apt-pkg/deb/deblistparser.h new file mode 100644 index 0000000..f02252d --- /dev/null +++ b/apt-pkg/deb/deblistparser.h @@ -0,0 +1,159 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Debian Package List Parser - This implements the abstract parser + interface for Debian package files + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_DEBLISTPARSER_H +#define PKGLIB_DEBLISTPARSER_H + +#include <apt-pkg/macros.h> +#include <apt-pkg/md5.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgcachegen.h> +#include <apt-pkg/tagfile.h> + +#include <string> +#include <vector> +#ifdef APT_PKG_EXPOSE_STRING_VIEW +#include <apt-pkg/string_view.h> +#endif + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/indexfile.h> +#endif + +class FileFd; + +class APT_HIDDEN debListParser : public pkgCacheListParser +{ + public: + +#ifdef APT_PKG_EXPOSE_STRING_VIEW + // Parser Helper + struct WordList + { + APT::StringView Str; + unsigned char Val; + }; +#endif + + private: + std::vector<std::string> forceEssential; + std::vector<std::string> forceImportant; + std::string MD5Buffer; + std::string myArch; + + protected: + pkgTagFile Tags; + pkgTagSection Section; + map_filesize_t iOffset; + + virtual bool ParseStatus(pkgCache::PkgIterator &Pkg,pkgCache::VerIterator &Ver); + bool ParseDepends(pkgCache::VerIterator &Ver, pkgTagSection::Key Key, + unsigned int Type); + bool ParseProvides(pkgCache::VerIterator &Ver); + +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN static bool GrabWord(APT::StringView Word,const WordList *List,unsigned char &Out); +#endif + APT_HIDDEN unsigned char ParseMultiArch(bool const showErrors); + + public: + + APT_PUBLIC static unsigned char GetPrio(std::string Str); + + // These all operate against the current section + virtual std::string Package() APT_OVERRIDE; + virtual bool ArchitectureAll() APT_OVERRIDE; +#ifdef APT_PKG_EXPOSE_STRING_VIEW + virtual APT::StringView Architecture() APT_OVERRIDE; + virtual APT::StringView Version() APT_OVERRIDE; +#endif + virtual bool NewVersion(pkgCache::VerIterator &Ver) APT_OVERRIDE; + virtual std::vector<std::string> AvailableDescriptionLanguages() APT_OVERRIDE; +#ifdef APT_PKG_EXPOSE_STRING_VIEW + virtual APT::StringView Description_md5() APT_OVERRIDE; +#endif + virtual unsigned short VersionHash() APT_OVERRIDE; + virtual bool SameVersion(unsigned short const Hash, pkgCache::VerIterator const &Ver) APT_OVERRIDE; + virtual bool UsePackage(pkgCache::PkgIterator &Pkg, + pkgCache::VerIterator &Ver) APT_OVERRIDE; + virtual map_filesize_t Offset() APT_OVERRIDE {return iOffset;}; + virtual map_filesize_t Size() APT_OVERRIDE {return Section.size();}; + + virtual bool Step() APT_OVERRIDE; + + APT_PUBLIC static const char *ParseDepends(const char *Start,const char *Stop, + std::string &Package,std::string &Ver,unsigned int &Op); + APT_PUBLIC static const char *ParseDepends(const char *Start,const char *Stop, + std::string &Package,std::string &Ver,unsigned int &Op, + bool const &ParseArchFlags); + APT_PUBLIC static const char *ParseDepends(const char *Start,const char *Stop, + std::string &Package,std::string &Ver,unsigned int &Op, + bool const &ParseArchFlags, bool const &StripMultiArch); + APT_PUBLIC static const char *ParseDepends(const char *Start,const char *Stop, + std::string &Package,std::string &Ver,unsigned int &Op, + bool const &ParseArchFlags, bool const &StripMultiArch, + bool const &ParseRestrictionsList); + APT_PUBLIC static const char *ParseDepends(const char *Start,const char *Stop, + std::string &Package,std::string &Ver,unsigned int &Op, + bool const &ParseArchFlags, bool const &StripMultiArch, + bool const &ParseRestrictionsList, + std::string const &Arch); + +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN static const char *ParseDepends(const char *Start,const char *Stop, + APT::StringView &Package, + APT::StringView &Ver,unsigned int &Op, + bool const ParseArchFlags = false, bool StripMultiArch = true, + bool const ParseRestrictionsList = false); + APT_HIDDEN static const char *ParseDepends(const char *Start,const char *Stop, + APT::StringView &Package, + APT::StringView &Ver,unsigned int &Op, + bool const ParseArchFlags, bool StripMultiArch, + bool const ParseRestrictionsList, + std::string const &Arch); +#endif + + APT_PUBLIC static const char *ConvertRelation(const char *I,unsigned int &Op); + + debListParser(FileFd *File); + virtual ~debListParser(); +}; + +class APT_HIDDEN debDebFileParser : public debListParser +{ + private: + std::string DebFile; + + public: + debDebFileParser(FileFd *File, std::string const &DebFile); + virtual bool UsePackage(pkgCache::PkgIterator &Pkg, + pkgCache::VerIterator &Ver) APT_OVERRIDE; +}; + +class APT_HIDDEN debTranslationsParser : public debListParser +{ + public: +#ifdef APT_PKG_EXPOSE_STRING_VIEW + // a translation can never be a real package + virtual APT::StringView Architecture() APT_OVERRIDE { return ""; } + virtual APT::StringView Version() APT_OVERRIDE { return ""; } +#endif + + debTranslationsParser(FileFd *File) + : debListParser(File) {}; +}; + +class APT_HIDDEN debStatusListParser : public debListParser +{ + public: + virtual bool ParseStatus(pkgCache::PkgIterator &Pkg,pkgCache::VerIterator &Ver); + debStatusListParser(FileFd *File) + : debListParser(File) {}; +}; +#endif diff --git a/apt-pkg/deb/debmetaindex.cc b/apt-pkg/deb/debmetaindex.cc new file mode 100644 index 0000000..f88076a --- /dev/null +++ b/apt-pkg/deb/debmetaindex.cc @@ -0,0 +1,1316 @@ +#include <config.h> + +#include <apt-pkg/acquire-item.h> +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/debindexfile.h> +#include <apt-pkg/debmetaindex.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/gpgv.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/metaindex.h> +#include <apt-pkg/pkgcachegen.h> +#include <apt-pkg/sourcelist.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile.h> + +#include <algorithm> +#include <map> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include <string.h> +#include <sys/stat.h> + +#include <apti18n.h> + +static std::string transformFingergrpints(std::string finger) /*{{{*/ +{ + std::transform(finger.begin(), finger.end(), finger.begin(), ::toupper); + if (finger.length() == 40) + { + if (finger.find_first_not_of("0123456789ABCDEF") == std::string::npos) + return finger; + } + else if (finger.length() == 41) + { + auto bang = finger.find_first_not_of("0123456789ABCDEF"); + if (bang == 40 && finger[bang] == '!') + return finger; + } + return ""; +} + /*}}}*/ +static std::string transformFingergrpintsWithFilenames(std::string const &finger) /*{{{*/ +{ + // no check for existence as we could be chrooting later or such things + if (finger.empty() == false && finger[0] == '/') + return finger; + return transformFingergrpints(finger); +} + /*}}}*/ +static std::string NormalizeSignedBy(std::string SignedBy, bool const SupportFilenames) /*{{{*/ +{ + // we could go all fancy and allow short/long/string matches as gpgv/apt-key does, + // but fingerprints are harder to fake than the others and this option is set once, + // not interactively all the time so easy to type is not really a concern. + std::transform(SignedBy.begin(), SignedBy.end(), SignedBy.begin(), [](char const c) { + return (isspace(c) == 0) ? c : ','; + }); + auto fingers = VectorizeString(SignedBy, ','); + auto const isAnEmptyString = [](std::string const &s) { return s.empty(); }; + fingers.erase(std::remove_if(fingers.begin(), fingers.end(), isAnEmptyString), fingers.end()); + if (unlikely(fingers.empty())) + return ""; + if (SupportFilenames) + std::transform(fingers.begin(), fingers.end(), fingers.begin(), transformFingergrpintsWithFilenames); + else + std::transform(fingers.begin(), fingers.end(), fingers.begin(), transformFingergrpints); + if (std::any_of(fingers.begin(), fingers.end(), isAnEmptyString)) + return ""; + std::stringstream os; + std::copy(fingers.begin(), fingers.end() - 1, std::ostream_iterator<std::string>(os, ",")); + os << *fingers.rbegin(); + return os.str(); +} + /*}}}*/ + +class APT_HIDDEN debReleaseIndexPrivate /*{{{*/ +{ + public: + struct APT_HIDDEN debSectionEntry + { + std::string const sourcesEntry; + std::string const Name; + std::vector<std::string> const Targets; + std::vector<std::string> const Architectures; + std::vector<std::string> const Languages; + bool const UsePDiffs; + std::string const UseByHash; + }; + + std::vector<debSectionEntry> DebEntries; + std::vector<debSectionEntry> DebSrcEntries; + + metaIndex::TriState CheckValidUntil; + time_t ValidUntilMin; + time_t ValidUntilMax; + + metaIndex::TriState CheckDate; + time_t DateMaxFuture; + time_t NotBefore; + + std::vector<std::string> Architectures; + std::vector<std::string> NoSupportForAll; + std::vector<std::string> SupportedComponents; + std::map<std::string, std::string> const ReleaseOptions; + + debReleaseIndexPrivate(std::map<std::string, std::string> const &Options) : CheckValidUntil(metaIndex::TRI_UNSET), ValidUntilMin(0), ValidUntilMax(0), CheckDate(metaIndex::TRI_UNSET), DateMaxFuture(0), NotBefore(0), ReleaseOptions(Options) {} +}; + /*}}}*/ +// ReleaseIndex::MetaIndex* - display helpers /*{{{*/ +std::string debReleaseIndex::MetaIndexInfo(const char *Type) const +{ + std::string Info = ::URI::ArchiveOnly(URI) + ' '; + if (Dist[Dist.size() - 1] == '/') + { + if (Dist != "/") + Info += Dist; + } + else + Info += Dist; + Info += " "; + Info += Type; + return Info; +} +std::string debReleaseIndex::Describe() const +{ + return MetaIndexInfo("Release"); +} + +std::string debReleaseIndex::MetaIndexFile(const char *Type) const +{ + return _config->FindDir("Dir::State::lists") + + URItoFileName(MetaIndexURI(Type)); +} +static std::string constructMetaIndexURI(std::string URI, std::string const &Dist, char const * const Type) +{ + if (Dist == "/") + ; + else if (Dist[Dist.size()-1] == '/') + URI += Dist; + else + URI += "dists/" + Dist + "/"; + return URI + Type; +} +std::string debReleaseIndex::MetaIndexURI(const char *Type) const +{ + return constructMetaIndexURI(URI, Dist, Type); +} + /*}}}*/ +// ReleaseIndex Con- and Destructors /*{{{*/ +debReleaseIndex::debReleaseIndex(std::string const &URI, std::string const &Dist, std::map<std::string, std::string> const &Options) : + metaIndex(URI, Dist, "deb"), d(new debReleaseIndexPrivate(Options)) +{} +debReleaseIndex::debReleaseIndex(std::string const &URI, std::string const &Dist, bool const pTrusted, std::map<std::string, std::string> const &Options) : + metaIndex(URI, Dist, "deb"), d(new debReleaseIndexPrivate(Options)) +{ + Trusted = pTrusted ? TRI_YES : TRI_NO; +} +debReleaseIndex::~debReleaseIndex() { + if (d != NULL) + delete d; +} + /*}}}*/ +// ReleaseIndex::GetIndexTargets /*{{{*/ +static void GetIndexTargetsFor(char const * const Type, std::string const &URI, std::string const &Dist, + std::vector<debReleaseIndexPrivate::debSectionEntry> const &entries, + std::vector<IndexTarget> &IndexTargets, std::map<std::string, std::string> const &ReleaseOptions) +{ + bool const flatArchive = (Dist[Dist.length() - 1] == '/'); + std::string const baseURI = constructMetaIndexURI(URI, Dist, ""); + + std::string DefCompressionTypes; + { + std::vector<std::string> types = APT::Configuration::getCompressionTypes(); + if (types.empty() == false) + { + std::ostringstream os; + std::copy(types.begin(), types.end()-1, std::ostream_iterator<std::string>(os, " ")); + os << *types.rbegin(); + DefCompressionTypes = os.str(); + } + } + std::string DefKeepCompressedAs; + { + std::vector<APT::Configuration::Compressor> comps = APT::Configuration::getCompressors(); + if (comps.empty() == false) + { + std::sort(comps.begin(), comps.end(), + [](APT::Configuration::Compressor const &a, APT::Configuration::Compressor const &b) { return a.Cost < b.Cost; }); + std::ostringstream os; + for (auto const &c : comps) + if (c.Cost != 0) + os << c.Extension.substr(1) << ' '; + DefKeepCompressedAs = os.str(); + } + DefKeepCompressedAs += "uncompressed"; + } + + std::vector<std::string> const NativeArchs = { _config->Find("APT::Architecture"), "implicit:all" }; + bool const GzipIndex = _config->FindB("Acquire::GzipIndexes", false); + for (std::vector<debReleaseIndexPrivate::debSectionEntry>::const_iterator E = entries.begin(); E != entries.end(); ++E) + { + for (std::vector<std::string>::const_iterator T = E->Targets.begin(); T != E->Targets.end(); ++T) + { +#define APT_T_CONFIG_STR(X, Y) _config->Find(std::string("Acquire::IndexTargets::") + Type + "::" + *T + "::" + (X), (Y)) +#define APT_T_CONFIG_BOOL(X, Y) _config->FindB(std::string("Acquire::IndexTargets::") + Type + "::" + *T + "::" + (X), (Y)) + std::string const tplMetaKey = APT_T_CONFIG_STR(flatArchive ? "flatMetaKey" : "MetaKey", ""); + std::string const tplShortDesc = APT_T_CONFIG_STR("ShortDescription", ""); + std::string const tplLongDesc = "$(SITE) " + APT_T_CONFIG_STR(flatArchive ? "flatDescription" : "Description", ""); + std::string const tplIdentifier = APT_T_CONFIG_STR("Identifier", *T); + bool const IsOptional = APT_T_CONFIG_BOOL("Optional", true); + bool const KeepCompressed = APT_T_CONFIG_BOOL("KeepCompressed", GzipIndex); + bool const DefaultEnabled = APT_T_CONFIG_BOOL("DefaultEnabled", true); + bool const UsePDiffs = APT_T_CONFIG_BOOL("PDiffs", E->UsePDiffs); + std::string const UseByHash = APT_T_CONFIG_STR("By-Hash", E->UseByHash); + std::string const CompressionTypes = APT_T_CONFIG_STR("CompressionTypes", DefCompressionTypes); + std::string KeepCompressedAs = APT_T_CONFIG_STR("KeepCompressedAs", ""); + std::string const FallbackOf = APT_T_CONFIG_STR("Fallback-Of", ""); +#undef APT_T_CONFIG_BOOL +#undef APT_T_CONFIG_STR + if (tplMetaKey.empty()) + continue; + + if (KeepCompressedAs.empty()) + KeepCompressedAs = DefKeepCompressedAs; + else + { + std::vector<std::string> const defKeep = VectorizeString(DefKeepCompressedAs, ' '); + std::vector<std::string> const valKeep = VectorizeString(KeepCompressedAs, ' '); + std::vector<std::string> keep; + for (auto const &val : valKeep) + { + if (val.empty()) + continue; + if (std::find(defKeep.begin(), defKeep.end(), val) == defKeep.end()) + continue; + keep.push_back(val); + } + if (std::find(keep.begin(), keep.end(), "uncompressed") == keep.end()) + keep.push_back("uncompressed"); + std::ostringstream os; + std::copy(keep.begin(), keep.end()-1, std::ostream_iterator<std::string>(os, " ")); + os << *keep.rbegin(); + KeepCompressedAs = os.str(); + } + + for (std::vector<std::string>::const_iterator L = E->Languages.begin(); L != E->Languages.end(); ++L) + { + if (*L == "none" && tplMetaKey.find("$(LANGUAGE)") != std::string::npos) + continue; + + for (std::vector<std::string>::const_iterator A = E->Architectures.begin(); A != E->Architectures.end(); ++A) + { + for (auto const &NativeArch: NativeArchs) + { + constexpr static auto BreakPoint = "$(NATIVE_ARCHITECTURE)"; + // available in templates + std::map<std::string, std::string> Options; + Options.insert(ReleaseOptions.begin(), ReleaseOptions.end()); + if (tplMetaKey.find("$(COMPONENT)") != std::string::npos) + Options.emplace("COMPONENT", E->Name); + if (tplMetaKey.find("$(LANGUAGE)") != std::string::npos) + Options.emplace("LANGUAGE", *L); + if (tplMetaKey.find("$(ARCHITECTURE)") != std::string::npos) + Options.emplace("ARCHITECTURE", (*A == "implicit:all") ? "all" : *A); + else if (tplMetaKey.find("$(NATIVE_ARCHITECTURE)") != std::string::npos) + Options.emplace("ARCHITECTURE", (NativeArch == "implicit:all") ? "all" : NativeArch); + if (tplMetaKey.find("$(NATIVE_ARCHITECTURE)") != std::string::npos) + Options.emplace("NATIVE_ARCHITECTURE", (NativeArch == "implicit:all") ? "all" : NativeArch); + + std::string MetaKey = tplMetaKey; + std::string ShortDesc = tplShortDesc; + std::string LongDesc = tplLongDesc; + std::string Identifier = tplIdentifier; + for (std::map<std::string, std::string>::const_iterator O = Options.begin(); O != Options.end(); ++O) + { + std::string const varname = "$(" + O->first + ")"; + MetaKey = SubstVar(MetaKey, varname, O->second); + ShortDesc = SubstVar(ShortDesc, varname, O->second); + LongDesc = SubstVar(LongDesc, varname, O->second); + Identifier = SubstVar(Identifier, varname, O->second); + } + + { + auto const dup = std::find_if(IndexTargets.begin(), IndexTargets.end(), [&](IndexTarget const &IT) { + return MetaKey == IT.MetaKey && baseURI == IT.Option(IndexTarget::BASE_URI) && + E->sourcesEntry == IT.Option(IndexTarget::SOURCESENTRY) && *T == IT.Option(IndexTarget::CREATED_BY); + }); + if (dup != IndexTargets.end()) + { + if (tplMetaKey.find(BreakPoint) == std::string::npos) + break; + continue; + } + } + + { + auto const dup = std::find_if(IndexTargets.begin(), IndexTargets.end(), [&](IndexTarget const &IT) { + return MetaKey == IT.MetaKey && baseURI == IT.Option(IndexTarget::BASE_URI) && + E->sourcesEntry == IT.Option(IndexTarget::SOURCESENTRY) && *T != IT.Option(IndexTarget::CREATED_BY); + }); + if (dup != IndexTargets.end()) + { + std::string const dupT = dup->Option(IndexTarget::CREATED_BY); + std::string const dupEntry = dup->Option(IndexTarget::SOURCESENTRY); + //TRANSLATOR: an identifier like Packages; Releasefile key indicating + // a file like main/binary-amd64/Packages; another identifier like Contents; + // filename and linenumber of the sources.list entry currently parsed + _error->Warning(_("Target %s wants to acquire the same file (%s) as %s from source %s"), + T->c_str(), MetaKey.c_str(), dupT.c_str(), dupEntry.c_str()); + if (tplMetaKey.find(BreakPoint) == std::string::npos) + break; + continue; + } + } + + { + auto const dup = std::find_if(IndexTargets.begin(), IndexTargets.end(), [&](IndexTarget const &T) { + return MetaKey == T.MetaKey && baseURI == T.Option(IndexTarget::BASE_URI) && + E->sourcesEntry != T.Option(IndexTarget::SOURCESENTRY); + }); + if (dup != IndexTargets.end()) + { + std::string const dupEntry = dup->Option(IndexTarget::SOURCESENTRY); + if (T->find("legacy") == std::string::npos) + { + //TRANSLATOR: an identifier like Packages; Releasefile key indicating + // a file like main/binary-amd64/Packages; filename and linenumber of + // two sources.list entries + _error->Warning(_("Target %s (%s) is configured multiple times in %s and %s"), + T->c_str(), MetaKey.c_str(), dupEntry.c_str(), E->sourcesEntry.c_str()); + } + if (tplMetaKey.find(BreakPoint) == std::string::npos) + break; + continue; + } + } + + // not available in templates, but in the indextarget + Options.insert(std::make_pair("IDENTIFIER", Identifier)); + Options.insert(std::make_pair("TARGET_OF", Type)); + Options.insert(std::make_pair("CREATED_BY", *T)); + Options.insert(std::make_pair("FALLBACK_OF", FallbackOf)); + Options.insert(std::make_pair("PDIFFS", UsePDiffs ? "yes" : "no")); + Options.insert(std::make_pair("BY_HASH", UseByHash)); + Options.insert(std::make_pair("DEFAULTENABLED", DefaultEnabled ? "yes" : "no")); + Options.insert(std::make_pair("COMPRESSIONTYPES", CompressionTypes)); + Options.insert(std::make_pair("KEEPCOMPRESSEDAS", KeepCompressedAs)); + Options.insert(std::make_pair("SOURCESENTRY", E->sourcesEntry)); + + bool IsOpt = IsOptional; + { + auto const arch = Options.find("ARCHITECTURE"); + if (arch != Options.end() && arch->second == "all") + { + // one of them must be implicit:all then + if (*A != "all" && NativeArch != "all") + IsOpt = true; + else // user used arch=all explicitly + Options.emplace("Force-Support-For-All", "yes"); + } + } + + IndexTarget Target( + MetaKey, + ShortDesc, + LongDesc, + baseURI + MetaKey, + IsOpt, + KeepCompressed, + Options + ); + IndexTargets.push_back(Target); + + if (tplMetaKey.find(BreakPoint) == std::string::npos) + break; + } + + if (tplMetaKey.find("$(ARCHITECTURE)") == std::string::npos) + break; + + } + + if (tplMetaKey.find("$(LANGUAGE)") == std::string::npos) + break; + + } + + } + } +} +std::vector<IndexTarget> debReleaseIndex::GetIndexTargets() const +{ + std::vector<IndexTarget> IndexTargets; + GetIndexTargetsFor("deb-src", URI, Dist, d->DebSrcEntries, IndexTargets, d->ReleaseOptions); + GetIndexTargetsFor("deb", URI, Dist, d->DebEntries, IndexTargets, d->ReleaseOptions); + return IndexTargets; +} + /*}}}*/ +void debReleaseIndex::AddComponent(std::string const &sourcesEntry, /*{{{*/ + bool const isSrc, std::string const &Name, + std::vector<std::string> const &Targets, + std::vector<std::string> const &Architectures, + std::vector<std::string> Languages, + bool const usePDiffs, std::string const &useByHash) +{ + if (Languages.empty() == true) + Languages.push_back("none"); + debReleaseIndexPrivate::debSectionEntry const entry = { + sourcesEntry, Name, Targets, Architectures, Languages, usePDiffs, useByHash + }; + if (isSrc) + d->DebSrcEntries.push_back(entry); + else + d->DebEntries.push_back(entry); +} + /*}}}*/ + +bool debReleaseIndex::Load(std::string const &Filename, std::string * const ErrorText)/*{{{*/ +{ + LoadedSuccessfully = TRI_NO; + FileFd Fd; + if (OpenMaybeClearSignedFile(Filename, Fd) == false) + return false; + + pkgTagFile TagFile(&Fd, Fd.Size()); + if (Fd.IsOpen() == false || Fd.Failed()) + { + if (ErrorText != NULL) + strprintf(*ErrorText, _("Unable to parse Release file %s"),Filename.c_str()); + return false; + } + + pkgTagSection Section; + const char *Start, *End; + if (TagFile.Step(Section) == false) + { + if (ErrorText != NULL) + strprintf(*ErrorText, _("No sections in Release file %s"), Filename.c_str()); + return false; + } + // FIXME: find better tag name + SupportsAcquireByHash = Section.FindB("Acquire-By-Hash", false); + + SetOrigin(Section.FindS("Origin")); + SetLabel(Section.FindS("Label")); + SetVersion(Section.FindS("Version")); + Suite = Section.FindS("Suite"); + Codename = Section.FindS("Codename"); + SetReleaseNotes(Section.FindS("Release-Notes")); + { + std::string const archs = Section.FindS("Architectures"); + if (archs.empty() == false) + d->Architectures = VectorizeString(archs, ' '); + } + { + std::string const targets = Section.FindS("No-Support-for-Architecture-all"); + if (targets.empty() == false) + d->NoSupportForAll = VectorizeString(targets, ' '); + } + for (auto const &comp: VectorizeString(Section.FindS("Components"), ' ')) + { + if (comp.empty()) + continue; + auto const pos = comp.find_last_of('/'); + if (pos != std::string::npos) // e.g. security.debian.org uses this style + d->SupportedComponents.push_back(comp.substr(pos + 1)); + d->SupportedComponents.push_back(std::move(comp)); + } + { + decltype(pkgCache::ReleaseFile::Flags) flags = 0; + Section.FindFlag("NotAutomatic", flags, pkgCache::Flag::NotAutomatic); + signed short defaultpin = 500; + if ((flags & pkgCache::Flag::NotAutomatic) == pkgCache::Flag::NotAutomatic) + { + Section.FindFlag("ButAutomaticUpgrades", flags, pkgCache::Flag::ButAutomaticUpgrades); + if ((flags & pkgCache::Flag::ButAutomaticUpgrades) == pkgCache::Flag::ButAutomaticUpgrades) + defaultpin = 100; + else + defaultpin = 1; + } + SetDefaultPin(defaultpin); + } + + bool FoundHashSum = false; + bool FoundStrongHashSum = false; + auto const SupportedHashes = HashString::SupportedHashes(); + for (int i=0; SupportedHashes[i] != NULL; i++) + { + if (!Section.Find(SupportedHashes[i], Start, End)) + continue; + + std::string Name; + std::string Hash; + unsigned long long Size; + while (Start < End) + { + if (!parseSumData(Start, End, Name, Hash, Size)) + return false; + + HashString const hs(SupportedHashes[i], Hash); + if (Entries.find(Name) == Entries.end()) + { + metaIndex::checkSum *Sum = new metaIndex::checkSum; + Sum->MetaKeyFilename = Name; + Sum->Size = Size; + Sum->Hashes.FileSize(Size); + APT_IGNORE_DEPRECATED_PUSH + Sum->Hash = hs; + APT_IGNORE_DEPRECATED_POP + Entries[Name] = Sum; + } + Entries[Name]->Hashes.push_back(hs); + FoundHashSum = true; + if (FoundStrongHashSum == false && hs.usable() == true) + FoundStrongHashSum = true; + } + } + + bool AuthPossible = false; + if(FoundHashSum == false) + _error->Warning(_("No Hash entry in Release file %s"), Filename.c_str()); + else if(FoundStrongHashSum == false) + _error->Warning(_("No Hash entry in Release file %s which is considered strong enough for security purposes"), Filename.c_str()); + else + AuthPossible = true; + + std::string const StrDate = Section.FindS("Date"); + if (RFC1123StrToTime(StrDate.c_str(), Date) == false) + { + _error->Warning( _("Invalid '%s' entry in Release file %s"), "Date", Filename.c_str()); + Date = 0; + } + + bool CheckDate = _config->FindB("Acquire::Check-Date", true); + if (d->CheckDate == metaIndex::TRI_NO) + CheckDate = false; + else if (d->CheckDate == metaIndex::TRI_YES) + CheckDate = true; + + if (CheckDate) + { + auto const Label = GetLabel(); + // get the user settings for this archive + time_t MaxFuture = d->DateMaxFuture; + if (MaxFuture == 0) + { + MaxFuture = _config->FindI("Acquire::Max-FutureTime", 10); + if (Label.empty() == false) + MaxFuture = _config->FindI(("Acquire::Max-FutureTime::" + Label).c_str(), MaxFuture); + } + + d->NotBefore = Date - MaxFuture; + + bool CheckValidUntil = _config->FindB("Acquire::Check-Valid-Until", true); + if (d->CheckValidUntil == metaIndex::TRI_NO) + CheckValidUntil = false; + else if (d->CheckValidUntil == metaIndex::TRI_YES) + CheckValidUntil = true; + + if (CheckValidUntil == true) + { + std::string const StrValidUntil = Section.FindS("Valid-Until"); + + // if we have a Valid-Until header in the Release file, use it as default + if (StrValidUntil.empty() == false) + { + if (RFC1123StrToTime(StrValidUntil.c_str(), ValidUntil) == false) + { + if (ErrorText != NULL) + strprintf(*ErrorText, _("Invalid '%s' entry in Release file %s"), "Valid-Until", Filename.c_str()); + return false; + } + } + auto const Label = GetLabel(); + // get the user settings for this archive and use what expires earlier + time_t MaxAge = d->ValidUntilMax; + if (MaxAge == 0) + { + MaxAge = _config->FindI("Acquire::Max-ValidTime", 0); + if (Label.empty() == false) + MaxAge = _config->FindI(("Acquire::Max-ValidTime::" + Label).c_str(), MaxAge); + } + time_t MinAge = d->ValidUntilMin; + if (MinAge == 0) + { + MinAge = _config->FindI("Acquire::Min-ValidTime", 0); + if (Label.empty() == false) + MinAge = _config->FindI(("Acquire::Min-ValidTime::" + Label).c_str(), MinAge); + } + + if (MinAge != 0 || ValidUntil != 0 || MaxAge != 0) + { + if (MinAge != 0 && ValidUntil != 0) + { + time_t const min_date = Date + MinAge; + if (ValidUntil < min_date) + ValidUntil = min_date; + } + if (MaxAge != 0 && Date != 0) + { + time_t const max_date = Date + MaxAge; + if (ValidUntil == 0 || ValidUntil > max_date) + ValidUntil = max_date; + } + } + } + } + + /* as the Release file is parsed only after it was verified, the Signed-By field + does not effect the current, but the "next" Release file */ + auto Sign = Section.FindS("Signed-By"); + if (Sign.empty() == false) + { + SignedBy = NormalizeSignedBy(Sign, false); + if (SignedBy.empty() && ErrorText != NULL) + strprintf(*ErrorText, _("Invalid '%s' entry in Release file %s"), "Signed-By", Filename.c_str()); + } + + if (AuthPossible) + LoadedSuccessfully = TRI_YES; + return AuthPossible; +} + /*}}}*/ +time_t debReleaseIndex::GetNotBefore() const /*{{{*/ +{ + return d->NotBefore; +} + /*}}}*/ +metaIndex * debReleaseIndex::UnloadedClone() const /*{{{*/ +{ + if (Trusted == TRI_NO) + return new debReleaseIndex(URI, Dist, false, d->ReleaseOptions); + else if (Trusted == TRI_YES) + return new debReleaseIndex(URI, Dist, true, d->ReleaseOptions); + else + return new debReleaseIndex(URI, Dist, d->ReleaseOptions); +} + /*}}}*/ +bool debReleaseIndex::parseSumData(const char *&Start, const char *End, /*{{{*/ + std::string &Name, std::string &Hash, unsigned long long &Size) +{ + Name = ""; + Hash = ""; + Size = 0; + /* Skip over the first blank */ + while ((*Start == '\t' || *Start == ' ' || *Start == '\n' || *Start == '\r') + && Start < End) + Start++; + if (Start >= End) + return false; + + /* Move EntryEnd to the end of the first entry (the hash) */ + const char *EntryEnd = Start; + while ((*EntryEnd != '\t' && *EntryEnd != ' ') + && EntryEnd < End) + EntryEnd++; + if (EntryEnd == End) + return false; + + Hash.append(Start, EntryEnd-Start); + + /* Skip over intermediate blanks */ + Start = EntryEnd; + while (*Start == '\t' || *Start == ' ') + Start++; + if (Start >= End) + return false; + + EntryEnd = Start; + /* Find the end of the second entry (the size) */ + while ((*EntryEnd != '\t' && *EntryEnd != ' ' ) + && EntryEnd < End) + EntryEnd++; + if (EntryEnd == End) + return false; + + Size = strtoull (Start, NULL, 10); + + /* Skip over intermediate blanks */ + Start = EntryEnd; + while (*Start == '\t' || *Start == ' ') + Start++; + if (Start >= End) + return false; + + EntryEnd = Start; + /* Find the end of the third entry (the filename) */ + while ((*EntryEnd != '\t' && *EntryEnd != ' ' && + *EntryEnd != '\n' && *EntryEnd != '\r') + && EntryEnd < End) + EntryEnd++; + + Name.append(Start, EntryEnd-Start); + Start = EntryEnd; //prepare for the next round + return true; +} + /*}}}*/ + +bool debReleaseIndex::GetIndexes(pkgAcquire *Owner, bool const &GetAll)/*{{{*/ +{ +#define APT_TARGET(X) IndexTarget("", X, MetaIndexInfo(X), MetaIndexURI(X), false, false, d->ReleaseOptions) + pkgAcqMetaClearSig * const TransactionManager = new pkgAcqMetaClearSig(Owner, + APT_TARGET("InRelease"), APT_TARGET("Release"), APT_TARGET("Release.gpg"), this); +#undef APT_TARGET + // special case for --print-uris + if (GetAll) + for (auto const &Target: GetIndexTargets()) + if (Target.Option(IndexTarget::FALLBACK_OF).empty()) + new pkgAcqIndex(Owner, TransactionManager, Target); + + return true; +} + /*}}}*/ +// ReleaseIndex::Set* TriState options /*{{{*/ +bool debReleaseIndex::SetTrusted(TriState const pTrusted) +{ + if (Trusted == TRI_UNSET) + Trusted = pTrusted; + else if (Trusted != pTrusted) + // TRANSLATOR: The first is an option name from sources.list manpage, the other two URI and Suite + return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), "Trusted", URI.c_str(), Dist.c_str()); + return true; +} +bool debReleaseIndex::SetCheckValidUntil(TriState const pCheckValidUntil) +{ + if (d->CheckValidUntil == TRI_UNSET) + d->CheckValidUntil = pCheckValidUntil; + else if (d->CheckValidUntil != pCheckValidUntil) + return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), "Check-Valid-Until", URI.c_str(), Dist.c_str()); + return true; +} +bool debReleaseIndex::SetValidUntilMin(time_t const Valid) +{ + if (d->ValidUntilMin == 0) + d->ValidUntilMin = Valid; + else if (d->ValidUntilMin != Valid) + return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), "Min-ValidTime", URI.c_str(), Dist.c_str()); + return true; +} +bool debReleaseIndex::SetValidUntilMax(time_t const Valid) +{ + if (d->ValidUntilMax == 0) + d->ValidUntilMax = Valid; + else if (d->ValidUntilMax != Valid) + return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), "Max-ValidTime", URI.c_str(), Dist.c_str()); + return true; +} +bool debReleaseIndex::SetCheckDate(TriState const pCheckDate) +{ + if (d->CheckDate == TRI_UNSET) + d->CheckDate = pCheckDate; + else if (d->CheckDate != pCheckDate) + return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), "Check-Date", URI.c_str(), Dist.c_str()); + return true; +} +bool debReleaseIndex::SetDateMaxFuture(time_t const DateMaxFuture) +{ + if (d->DateMaxFuture == 0) + d->DateMaxFuture = DateMaxFuture; + else if (d->DateMaxFuture != DateMaxFuture) + return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), "Date-Max-Future", URI.c_str(), Dist.c_str()); + return true; +} +bool debReleaseIndex::SetSignedBy(std::string const &pSignedBy) +{ + if (SignedBy.empty() == true && pSignedBy.empty() == false) + { + SignedBy = NormalizeSignedBy(pSignedBy, true); + if (SignedBy.empty()) + _error->Error(_("Invalid value set for option %s regarding source %s %s (%s)"), "Signed-By", URI.c_str(), Dist.c_str(), "not a fingerprint"); + } + else + { + auto const normalSignedBy = NormalizeSignedBy(pSignedBy, true); + if (normalSignedBy != SignedBy) + return _error->Error(_("Conflicting values set for option %s regarding source %s %s: %s != %s"), "Signed-By", URI.c_str(), Dist.c_str(), SignedBy.c_str(), normalSignedBy.c_str()); + } + return true; +} + /*}}}*/ +// ReleaseIndex::IsTrusted /*{{{*/ +bool debReleaseIndex::IsTrusted() const +{ + if (Trusted == TRI_YES) + return true; + else if (Trusted == TRI_NO) + return false; + + + if(_config->FindB("APT::Authentication::TrustCDROM", false)) + if(URI.substr(0,strlen("cdrom:")) == "cdrom:") + return true; + + if (FileExists(MetaIndexFile("Release.gpg"))) + return true; + + return FileExists(MetaIndexFile("InRelease")); +} + /*}}}*/ +bool debReleaseIndex::IsArchitectureSupported(std::string const &arch) const/*{{{*/ +{ + if (d->Architectures.empty()) + return true; + return std::find(d->Architectures.begin(), d->Architectures.end(), arch) != d->Architectures.end(); +} + /*}}}*/ +bool debReleaseIndex::IsArchitectureAllSupportedFor(IndexTarget const &target) const/*{{{*/ +{ + if (target.Options.find("Force-Support-For-All") != target.Options.end()) + return true; + if (IsArchitectureSupported("all") == false) + return false; + if (d->NoSupportForAll.empty()) + return true; + return std::find(d->NoSupportForAll.begin(), d->NoSupportForAll.end(), target.Option(IndexTarget::CREATED_BY)) == d->NoSupportForAll.end(); +} + /*}}}*/ +bool debReleaseIndex::HasSupportForComponent(std::string const &component) const/*{{{*/ +{ + if (d->SupportedComponents.empty()) + return true; + return std::find(d->SupportedComponents.begin(), d->SupportedComponents.end(), component) != d->SupportedComponents.end(); +} + /*}}}*/ +std::vector <pkgIndexFile *> *debReleaseIndex::GetIndexFiles() /*{{{*/ +{ + if (Indexes != NULL) + return Indexes; + + Indexes = new std::vector<pkgIndexFile*>(); + bool const istrusted = IsTrusted(); + for (auto const &T: GetIndexTargets()) + { + std::string const TargetName = T.Option(IndexTarget::CREATED_BY); + if (TargetName == "Packages") + Indexes->push_back(new debPackagesIndex(T, istrusted)); + else if (TargetName == "Sources") + Indexes->push_back(new debSourcesIndex(T, istrusted)); + else if (TargetName == "Translations") + Indexes->push_back(new debTranslationsIndex(T)); + } + return Indexes; +} + /*}}}*/ +std::map<std::string, std::string> debReleaseIndex::GetReleaseOptions() +{ + return d->ReleaseOptions; +} + +static bool ReleaseFileName(debReleaseIndex const * const That, std::string &ReleaseFile)/*{{{*/ +{ + ReleaseFile = That->MetaIndexFile("InRelease"); + bool releaseExists = false; + if (FileExists(ReleaseFile) == true) + releaseExists = true; + else + { + ReleaseFile = That->MetaIndexFile("Release"); + if (FileExists(ReleaseFile)) + releaseExists = true; + } + return releaseExists; +} + /*}}}*/ +bool debReleaseIndex::Merge(pkgCacheGenerator &Gen,OpProgress * /*Prog*/) const/*{{{*/ +{ + std::string ReleaseFile; + bool const releaseExists = ReleaseFileName(this, ReleaseFile); + + ::URI Tmp(URI); + if (Gen.SelectReleaseFile(ReleaseFile, Tmp.Host) == false) + return _error->Error("Problem with SelectReleaseFile %s", ReleaseFile.c_str()); + + if (releaseExists == false) + return true; + + FileFd Rel; + // Beware: The 'Release' file might be clearsigned in case the + // signature for an 'InRelease' file couldn't be checked + if (OpenMaybeClearSignedFile(ReleaseFile, Rel) == false) + return false; + + // Store the IMS information + pkgCache::RlsFileIterator File = Gen.GetCurRlsFile(); + pkgCacheGenerator::Dynamic<pkgCache::RlsFileIterator> DynFile(File); + // Rel can't be used as this is potentially a temporary file + struct stat Buf; + if (stat(ReleaseFile.c_str(), &Buf) != 0) + return _error->Errno("fstat", "Unable to stat file %s", ReleaseFile.c_str()); + File->Size = Buf.st_size; + File->mtime = Buf.st_mtime; + + pkgTagFile TagFile(&Rel, Rel.Size()); + pkgTagSection Section; + if (Rel.IsOpen() == false || Rel.Failed() || TagFile.Step(Section) == false) + return false; + + std::string data; + #define APT_INRELEASE(TYPE, TAG, STORE) \ + data = Section.FindS(TAG); \ + if (data.empty() == false) \ + { \ + map_stringitem_t const storage = Gen.StoreString(pkgCacheGenerator::TYPE, data); \ + if (storage == 0) return false; \ + STORE = storage; \ + } + APT_INRELEASE(MIXED, "Suite", File->Archive) + APT_INRELEASE(VERSIONNUMBER, "Version", File->Version) + APT_INRELEASE(MIXED, "Origin", File->Origin) + APT_INRELEASE(MIXED, "Codename", File->Codename) + APT_INRELEASE(MIXED, "Label", File->Label) + #undef APT_INRELEASE + Section.FindFlag("NotAutomatic", File->Flags, pkgCache::Flag::NotAutomatic); + Section.FindFlag("ButAutomaticUpgrades", File->Flags, pkgCache::Flag::ButAutomaticUpgrades); + Section.FindFlag("Packages-Require-Authorization", File->Flags, pkgCache::Flag::PackagesRequireAuthorization); + + return true; +} + /*}}}*/ +// ReleaseIndex::FindInCache - Find this index /*{{{*/ +pkgCache::RlsFileIterator debReleaseIndex::FindInCache(pkgCache &Cache, bool const ModifyCheck) const +{ + std::string ReleaseFile; + bool const releaseExists = ReleaseFileName(this, ReleaseFile); + + pkgCache::RlsFileIterator File = Cache.RlsFileBegin(); + for (; File.end() == false; ++File) + { + if (File->FileName == 0 || ReleaseFile != File.FileName()) + continue; + + // empty means the file does not exist by "design" + if (ModifyCheck == false || (releaseExists == false && File->Size == 0)) + return File; + + struct stat St; + if (stat(File.FileName(),&St) != 0) + { + if (_config->FindB("Debug::pkgCacheGen", false)) + std::clog << "ReleaseIndex::FindInCache - stat failed on " << File.FileName() << std::endl; + return pkgCache::RlsFileIterator(Cache); + } + if ((unsigned)St.st_size != File->Size || St.st_mtime != File->mtime) + { + if (_config->FindB("Debug::pkgCacheGen", false)) + std::clog << "ReleaseIndex::FindInCache - size (" << St.st_size << " <> " << File->Size + << ") or mtime (" << St.st_mtime << " <> " << File->mtime + << ") doesn't match for " << File.FileName() << std::endl; + return pkgCache::RlsFileIterator(Cache); + } + return File; + } + + return File; +} + /*}}}*/ + +class APT_HIDDEN debSLTypeDebian : public pkgSourceList::Type /*{{{*/ +{ + static std::vector<std::string> getDefaultSetOf(std::string const &Name, + std::map<std::string, std::string> const &Options, std::vector<std::string> const &defaultValues) + { + auto const val = Options.find(Name); + if (val != Options.end()) + return VectorizeString(val->second, ','); + return defaultValues; + } + static std::vector<std::string> applyPlusMinusOptions(std::string const &Name, + std::map<std::string, std::string> const &Options, std::vector<std::string> &&Values) + { + auto val = Options.find(Name + "+"); + if (val != Options.end()) + { + std::vector<std::string> const plus = VectorizeString(val->second, ','); + std::copy_if(plus.begin(), plus.end(), std::back_inserter(Values), [&Values](std::string const &v) { + return std::find(Values.begin(), Values.end(), v) == Values.end(); + }); + } + if ((val = Options.find(Name + "-")) != Options.end()) + { + std::vector<std::string> const minus = VectorizeString(val->second, ','); + Values.erase(std::remove_if(Values.begin(), Values.end(), [&minus](std::string const &v) { + return std::find(minus.begin(), minus.end(), v) != minus.end(); + }), Values.end()); + } + return Values; + } + static std::vector<std::string> parsePlusMinusOptions(std::string const &Name, + std::map<std::string, std::string> const &Options, std::vector<std::string> const &defaultValues) + { + return applyPlusMinusOptions(Name, Options, getDefaultSetOf(Name, Options, defaultValues)); + } + static std::vector<std::string> parsePlusMinusArchOptions(std::string const &Name, + std::map<std::string, std::string> const &Options) + { + auto Values = getDefaultSetOf(Name, Options, APT::Configuration::getArchitectures()); + // all is a very special architecture users shouldn't be concerned with explicitly + // but if the user does, do not override the choice + auto const val = Options.find(Name + "-"); + if (val != Options.end()) + { + std::vector<std::string> const minus = VectorizeString(val->second, ','); + if (std::find(minus.begin(), minus.end(), "all") != minus.end()) + return applyPlusMinusOptions(Name, Options, std::move(Values)); + } + Values = applyPlusMinusOptions(Name, Options, std::move(Values)); + if (std::find(Values.begin(), Values.end(), "all") == Values.end()) + Values.push_back("implicit:all"); + return Values; + } + static std::vector<std::string> parsePlusMinusTargetOptions(char const * const Name, + std::map<std::string, std::string> const &Options) + { + std::vector<std::string> const alltargets = _config->FindVector(std::string("Acquire::IndexTargets::") + Name, "", true); + std::vector<std::string> deftargets; + deftargets.reserve(alltargets.size()); + std::copy_if(alltargets.begin(), alltargets.end(), std::back_inserter(deftargets), [&](std::string const &t) { + std::string c = "Acquire::IndexTargets::"; + c.append(Name).append("::").append(t).append("::DefaultEnabled"); + return _config->FindB(c, true); + }); + std::vector<std::string> mytargets = parsePlusMinusOptions("target", Options, deftargets); + for (auto const &target : alltargets) + { + std::map<std::string, std::string>::const_iterator const opt = Options.find(target); + if (opt == Options.end()) + continue; + auto const idMatch = [&](std::string const &t) { + return target == _config->Find(std::string("Acquire::IndexTargets::") + Name + "::" + t + "::Identifier", t); + }; + if (StringToBool(opt->second)) + std::copy_if(alltargets.begin(), alltargets.end(), std::back_inserter(mytargets), idMatch); + else + mytargets.erase(std::remove_if(mytargets.begin(), mytargets.end(), idMatch), mytargets.end()); + } + // if we can't order it in a 1000 steps we give up… probably a cycle + for (auto i = 0; i < 1000; ++i) + { + bool Changed = false; + for (auto t = mytargets.begin(); t != mytargets.end(); ++t) + { + std::string const fallback = _config->Find(std::string("Acquire::IndexTargets::") + Name + "::" + *t + "::Fallback-Of"); + if (fallback.empty()) + continue; + auto const faller = std::find(mytargets.begin(), mytargets.end(), fallback); + if (faller == mytargets.end() || faller < t) + continue; + Changed = true; + auto const tv = *t; + mytargets.erase(t); + mytargets.emplace_back(tv); + break; + } + if (Changed == false) + break; + } + // remove duplicates without changing the order (in first appearance) + { + std::set<std::string> seenOnce; + mytargets.erase(std::remove_if(mytargets.begin(), mytargets.end(), [&](std::string const &t) { + return seenOnce.insert(t).second == false; + }), mytargets.end()); + } + return mytargets; + } + + metaIndex::TriState GetTriStateOption(std::map<std::string, std::string>const &Options, char const * const name) const + { + std::map<std::string, std::string>::const_iterator const opt = Options.find(name); + if (opt != Options.end()) + return StringToBool(opt->second, false) ? metaIndex::TRI_YES : metaIndex::TRI_NO; + return metaIndex::TRI_DONTCARE; + } + + static time_t GetTimeOption(std::map<std::string, std::string>const &Options, char const * const name) + { + std::map<std::string, std::string>::const_iterator const opt = Options.find(name); + if (opt == Options.end()) + return 0; + return strtoull(opt->second.c_str(), NULL, 10); + } + + static bool GetBoolOption(std::map<std::string, std::string> const &Options, char const * const name, bool const defVal) + { + std::map<std::string, std::string>::const_iterator const opt = Options.find(name); + if (opt == Options.end()) + return defVal; + return StringToBool(opt->second, defVal); + } + + static std::vector<std::string> GetMapKeys(std::map<std::string, std::string> const &Options) + { + std::vector<std::string> ret; + ret.reserve(Options.size()); + for (auto &&O: Options) + ret.emplace_back(O.first); + std::sort(ret.begin(), ret.end()); + return ret; + } + + static bool MapsAreEqual(std::map<std::string, std::string> const &OptionsA, + std::map<std::string, std::string> const &OptionsB, + std::string const &URI, std::string const &Dist) + { + auto const KeysA = GetMapKeys(OptionsA); + auto const KeysB = GetMapKeys(OptionsB); + auto const m = std::mismatch(KeysA.begin(), KeysA.end(), KeysB.begin()); + if (m.first != KeysA.end()) + { + if (std::find(KeysB.begin(), KeysB.end(), *m.first) == KeysB.end()) + return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), m.first->c_str(), "<set>", "<unset>"); + else + return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), m.second->c_str(), "<set>", "<unset>"); + } + if (m.second != KeysB.end()) + { + if (std::find(KeysA.begin(), KeysA.end(), *m.second) == KeysA.end()) + return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), m.first->c_str(), "<set>", "<unset>"); + else + return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), m.second->c_str(), "<set>", "<unset>"); + } + for (auto&& key: KeysA) + { + if (key == "BASE_URI" || key == "REPO_URI" || key == "SITE" || key == "RELEASE") + continue; + auto const a = OptionsA.find(key); + auto const b = OptionsB.find(key); + if (unlikely(a == OptionsA.end() || b == OptionsB.end()) || a->second != b->second) + return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), key.c_str(), URI.c_str(), Dist.c_str()); + } + return true; + } + + static debReleaseIndex * GetDebReleaseIndexBy(std::vector<metaIndex *> &List, std::string const &URI, + std::string const &Dist, std::map<std::string, std::string> const &Options) + { + std::map<std::string, std::string> ReleaseOptions{{ + {"BASE_URI", constructMetaIndexURI(URI, Dist, "")}, + {"REPO_URI", URI}, + {"SITE", ::URI::ArchiveOnly(URI)}, + {"RELEASE", (Dist == "/") ? "" : Dist}, + }}; + if (GetBoolOption(Options, "allow-insecure", _config->FindB("Acquire::AllowInsecureRepositories"))) + ReleaseOptions.emplace("ALLOW_INSECURE", "true"); + if (GetBoolOption(Options, "allow-weak", _config->FindB("Acquire::AllowWeakRepositories"))) + ReleaseOptions.emplace("ALLOW_WEAK", "true"); + if (GetBoolOption(Options, "allow-downgrade-to-insecure", _config->FindB("Acquire::AllowDowngradeToInsecureRepositories"))) + ReleaseOptions.emplace("ALLOW_DOWNGRADE_TO_INSECURE", "true"); + + auto InReleasePath = Options.find("inrelease-path"); + if (InReleasePath != Options.end()) + ReleaseOptions.emplace("INRELEASE_PATH", InReleasePath->second); + + debReleaseIndex * Deb = nullptr; + std::string const FileName = URItoFileName(constructMetaIndexURI(URI, Dist, "Release")); + for (auto const &I: List) + { + // We only worry about debian entries here + if (strcmp(I->GetType(), "deb") != 0) + continue; + + auto const D = dynamic_cast<debReleaseIndex*>(I); + if (unlikely(D == nullptr)) + continue; + + /* This check ensures that there will be only one Release file + queued for all the Packages files and Sources files it + corresponds to. */ + if (URItoFileName(D->MetaIndexURI("Release")) == FileName) + { + if (MapsAreEqual(ReleaseOptions, D->GetReleaseOptions(), URI, Dist) == false) + return nullptr; + Deb = D; + break; + } + } + + // No currently created Release file indexes this entry, so we create a new one. + if (Deb == nullptr) + { + Deb = new debReleaseIndex(URI, Dist, ReleaseOptions); + List.push_back(Deb); + } + return Deb; + } + + protected: + + bool CreateItemInternal(std::vector<metaIndex *> &List, std::string const &URI, + std::string const &Dist, std::string const &Section, + bool const &IsSrc, std::map<std::string, std::string> const &Options) const + { + auto const Deb = GetDebReleaseIndexBy(List, URI, Dist, Options); + if (Deb == nullptr) + return false; + + bool const UsePDiffs = GetBoolOption(Options, "pdiffs", _config->FindB("Acquire::PDiffs", true)); + + std::string UseByHash = _config->Find("APT::Acquire::By-Hash", "yes"); + UseByHash = _config->Find("Acquire::By-Hash", UseByHash); + { + std::string const host = ::URI(URI).Host; + if (host.empty() == false) + { + UseByHash = _config->Find("APT::Acquire::" + host + "::By-Hash", UseByHash); + UseByHash = _config->Find("Acquire::" + host + "::By-Hash", UseByHash); + } + std::map<std::string, std::string>::const_iterator const opt = Options.find("by-hash"); + if (opt != Options.end()) + UseByHash = opt->second; + } + + auto const entry = Options.find("sourceslist-entry"); + Deb->AddComponent( + entry->second, + IsSrc, + Section, + parsePlusMinusTargetOptions(Name, Options), + parsePlusMinusArchOptions("arch", Options), + parsePlusMinusOptions("lang", Options, APT::Configuration::getLanguages(true)), + UsePDiffs, + UseByHash + ); + + if (Deb->SetTrusted(GetTriStateOption(Options, "trusted")) == false || + Deb->SetCheckValidUntil(GetTriStateOption(Options, "check-valid-until")) == false || + Deb->SetValidUntilMax(GetTimeOption(Options, "valid-until-max")) == false || + Deb->SetValidUntilMin(GetTimeOption(Options, "valid-until-min")) == false || + Deb->SetCheckDate(GetTriStateOption(Options, "check-date")) == false || + Deb->SetDateMaxFuture(GetTimeOption(Options, "date-max-future")) == false) + return false; + + std::map<std::string, std::string>::const_iterator const signedby = Options.find("signed-by"); + if (signedby == Options.end()) + { + bool alreadySet = false; + std::string filename; + if (ReleaseFileName(Deb, filename)) + { + auto OldDeb = Deb->UnloadedClone(); + _error->PushToStack(); + OldDeb->Load(filename, nullptr); + bool const goodLoad = _error->PendingError() == false; + _error->RevertToStack(); + if (goodLoad) + { + if (OldDeb->GetValidUntil() > 0) + { + time_t const invalid_since = time(NULL) - OldDeb->GetValidUntil(); + if (invalid_since <= 0) + { + Deb->SetSignedBy(OldDeb->GetSignedBy()); + alreadySet = true; + } + } + } + delete OldDeb; + } + if (alreadySet == false && Deb->SetSignedBy("") == false) + return false; + } + else + { + if (Deb->SetSignedBy(signedby->second) == false) + return false; + } + + return true; + } + + debSLTypeDebian(char const * const Name, char const * const Label) : Type(Name, Label) + { + } +}; + /*}}}*/ +class APT_HIDDEN debSLTypeDeb : public debSLTypeDebian /*{{{*/ +{ + public: + + bool CreateItem(std::vector<metaIndex *> &List, std::string const &URI, + std::string const &Dist, std::string const &Section, + std::map<std::string, std::string> const &Options) const APT_OVERRIDE + { + return CreateItemInternal(List, URI, Dist, Section, false, Options); + } + + debSLTypeDeb() : debSLTypeDebian("deb", "Debian binary tree") + { + } +}; + /*}}}*/ +class APT_HIDDEN debSLTypeDebSrc : public debSLTypeDebian /*{{{*/ +{ + public: + + bool CreateItem(std::vector<metaIndex *> &List, std::string const &URI, + std::string const &Dist, std::string const &Section, + std::map<std::string, std::string> const &Options) const APT_OVERRIDE + { + return CreateItemInternal(List, URI, Dist, Section, true, Options); + } + + debSLTypeDebSrc() : debSLTypeDebian("deb-src", "Debian source tree") + { + } +}; + /*}}}*/ + +APT_HIDDEN debSLTypeDeb _apt_DebType; +APT_HIDDEN debSLTypeDebSrc _apt_DebSrcType; diff --git a/apt-pkg/deb/debmetaindex.h b/apt-pkg/deb/debmetaindex.h new file mode 100644 index 0000000..864ac3e --- /dev/null +++ b/apt-pkg/deb/debmetaindex.h @@ -0,0 +1,78 @@ +#ifndef PKGLIB_DEBMETAINDEX_H +#define PKGLIB_DEBMETAINDEX_H + +#include <apt-pkg/macros.h> +#include <apt-pkg/metaindex.h> + +#include <map> +#include <string> +#include <vector> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/sourcelist.h> +#endif +#ifndef APT_10_CLEANER_HEADERS +#include <apt-pkg/init.h> +#endif + +class pkgAcquire; +class pkgIndexFile; +class IndexTarget; +class pkgCacheGenerator; +class OpProgress; +class debReleaseIndexPrivate; + +class APT_HIDDEN debReleaseIndex : public metaIndex +{ + debReleaseIndexPrivate * const d; + + APT_HIDDEN bool parseSumData(const char *&Start, const char *End, std::string &Name, + std::string &Hash, unsigned long long &Size); + public: + + APT_HIDDEN std::string MetaIndexInfo(const char *Type) const; + APT_HIDDEN std::string MetaIndexFile(const char *Types) const; + APT_HIDDEN std::string MetaIndexURI(const char *Type) const; + + debReleaseIndex(std::string const &URI, std::string const &Dist, std::map<std::string,std::string> const &Options); + debReleaseIndex(std::string const &URI, std::string const &Dist, bool const Trusted, std::map<std::string,std::string> const &Options); + virtual ~debReleaseIndex(); + + virtual std::string ArchiveURI(std::string const &File) const APT_OVERRIDE {return URI + File;}; + virtual bool GetIndexes(pkgAcquire *Owner, bool const &GetAll=false) APT_OVERRIDE; + virtual std::vector<IndexTarget> GetIndexTargets() const APT_OVERRIDE; + + virtual std::string Describe() const APT_OVERRIDE; + virtual pkgCache::RlsFileIterator FindInCache(pkgCache &Cache, bool const ModifyCheck) const APT_OVERRIDE; + virtual bool Merge(pkgCacheGenerator &Gen,OpProgress *Prog) const APT_OVERRIDE; + + virtual bool Load(std::string const &Filename, std::string * const ErrorText) APT_OVERRIDE; + virtual metaIndex * UnloadedClone() const APT_OVERRIDE; + + virtual std::vector <pkgIndexFile *> *GetIndexFiles() APT_OVERRIDE; + + bool SetTrusted(TriState const Trusted); + bool SetCheckValidUntil(TriState const Trusted); + bool SetValidUntilMin(time_t const Valid); + bool SetValidUntilMax(time_t const Valid); + bool SetCheckDate(TriState const CheckDate); + bool SetDateMaxFuture(time_t const DateMaxFuture); + bool SetSignedBy(std::string const &SignedBy); + std::map<std::string, std::string> GetReleaseOptions(); + + virtual bool IsTrusted() const APT_OVERRIDE; + bool IsArchitectureSupported(std::string const &arch) const; + bool IsArchitectureAllSupportedFor(IndexTarget const &target) const; + bool HasSupportForComponent(std::string const &component) const; + + APT_PURE time_t GetNotBefore() const; + + void AddComponent(std::string const &sourcesEntry, + bool const isSrc, std::string const &Name, + std::vector<std::string> const &Targets, + std::vector<std::string> const &Architectures, + std::vector<std::string> Languages, + bool const usePDiffs, std::string const &useByHash); +}; + +#endif diff --git a/apt-pkg/deb/debrecords.cc b/apt-pkg/deb/debrecords.cc new file mode 100644 index 0000000..22ac219 --- /dev/null +++ b/apt-pkg/deb/debrecords.cc @@ -0,0 +1,227 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Debian Package Records - Parser for debian package records + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/debindexfile.h> +#include <apt-pkg/debrecords.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile.h> + +#include <algorithm> +#include <sstream> +#include <string> +#include <vector> +#include <langinfo.h> +#include <string.h> + +#include <apti18n.h> + /*}}}*/ + +using std::string; + +// RecordParser::debRecordParser - Constructor /*{{{*/ +debRecordParser::debRecordParser(string FileName,pkgCache &Cache) : + debRecordParserBase(), d(NULL), File(FileName, FileFd::ReadOnly, FileFd::Extension), + Tags(&File, std::max(Cache.Head().MaxVerFileSize, Cache.Head().MaxDescFileSize) + 200) +{ +} + /*}}}*/ +// RecordParser::Jump - Jump to a specific record /*{{{*/ +bool debRecordParser::Jump(pkgCache::VerFileIterator const &Ver) +{ + if (Ver.end() == true) + return false; + return Tags.Jump(Section,Ver->Offset); +} +bool debRecordParser::Jump(pkgCache::DescFileIterator const &Desc) +{ + if (Desc.end() == true) + return false; + return Tags.Jump(Section,Desc->Offset); +} + /*}}}*/ +debRecordParser::~debRecordParser() {} + +debRecordParserBase::debRecordParserBase() : Parser(), d(NULL) {} +// RecordParserBase::FileName - Return the archive filename on the site /*{{{*/ +string debRecordParserBase::FileName() +{ + return Section.FindS("Filename"); +} + /*}}}*/ +// RecordParserBase::Name - Return the package name /*{{{*/ +string debRecordParserBase::Name() +{ + string Result = Section.FindS("Package"); + + // Normalize mixed case package names to lower case, like dpkg does + // See Bug#807012 for details + std::transform(Result.begin(), Result.end(), Result.begin(), tolower_ascii); + + return Result; +} + /*}}}*/ +// RecordParserBase::Homepage - Return the package homepage /*{{{*/ +string debRecordParserBase::Homepage() +{ + return Section.FindS("Homepage"); +} + /*}}}*/ +// RecordParserBase::Hashes - return the available archive hashes /*{{{*/ +HashStringList debRecordParserBase::Hashes() const +{ + HashStringList hashes; + for (char const * const * type = HashString::SupportedHashes(); *type != NULL; ++type) + { + std::string const hash = Section.FindS(*type); + if (hash.empty() == false) + hashes.push_back(HashString(*type, hash)); + } + auto const size = Section.FindULL("Size", 0); + if (size != 0) + hashes.FileSize(size); + return hashes; +} + /*}}}*/ +// RecordParserBase::Maintainer - Return the maintainer email /*{{{*/ +string debRecordParserBase::Maintainer() +{ + return Section.FindS("Maintainer"); +} + /*}}}*/ +// RecordParserBase::RecordField - Return the value of an arbitrary field /*{{*/ +string debRecordParserBase::RecordField(const char *fieldName) +{ + return Section.FindS(fieldName); +} + /*}}}*/ +// RecordParserBase::ShortDesc - Return a 1 line description /*{{{*/ +string debRecordParserBase::ShortDesc(std::string const &lang) +{ + string const Res = LongDesc(lang); + if (Res.empty() == true) + return ""; + string::size_type const Pos = Res.find('\n'); + if (Pos == string::npos) + return Res; + return string(Res,0,Pos); +} + /*}}}*/ +// RecordParserBase::LongDesc - Return a longer description /*{{{*/ +string debRecordParserBase::LongDesc(std::string const &lang) +{ + string orig; + if (lang.empty() == true) + { + std::vector<string> const lang = APT::Configuration::getLanguages(); + for (std::vector<string>::const_iterator l = lang.begin(); + l != lang.end(); ++l) + { + std::string const tagname = "Description-" + *l; + orig = Section.FindS(tagname.c_str()); + if (orig.empty() == false) + break; + else if (*l == "en") + { + orig = Section.FindS("Description"); + if (orig.empty() == false) + break; + } + } + if (orig.empty() == true) + orig = Section.FindS("Description"); + } + else + { + std::string const tagname = "Description-" + lang; + orig = Section.FindS(tagname.c_str()); + if (orig.empty() == true && lang == "en") + orig = Section.FindS("Description"); + } + + char const * const codeset = nl_langinfo(CODESET); + if (strcmp(codeset,"UTF-8") != 0) { + string dest; + UTF8ToCodeset(codeset, orig, &dest); + return dest; + } + + return orig; +} + /*}}}*/ + +static const char * const SourceVerSeparators = " ()"; +// RecordParserBase::SourcePkg - Return the source package name if any /*{{{*/ +string debRecordParserBase::SourcePkg() +{ + string Res = Section.FindS("Source"); + string::size_type Pos = Res.find_first_of(SourceVerSeparators); + if (Pos == string::npos) + return Res; + return string(Res,0,Pos); +} + /*}}}*/ +// RecordParserBase::SourceVer - Return the source version number if present /*{{{*/ +string debRecordParserBase::SourceVer() +{ + string Pkg = Section.FindS("Source"); + string::size_type Pos = Pkg.find_first_of(SourceVerSeparators); + if (Pos == string::npos) + return ""; + + string::size_type VerStart = Pkg.find_first_not_of(SourceVerSeparators, Pos); + if(VerStart == string::npos) + return ""; + + string::size_type VerEnd = Pkg.find_first_of(SourceVerSeparators, VerStart); + if(VerEnd == string::npos) + // Corresponds to the case of, e.g., "foo (1.2" without a closing + // paren. Be liberal and guess what it means. + return string(Pkg, VerStart); + else + return string(Pkg, VerStart, VerEnd - VerStart); +} + /*}}}*/ +// RecordParserBase::GetRec - Return the whole record /*{{{*/ +void debRecordParserBase::GetRec(const char *&Start,const char *&Stop) +{ + Section.GetSection(Start,Stop); +} + /*}}}*/ +debRecordParserBase::~debRecordParserBase() {} + +bool debDebFileRecordParser::LoadContent() +{ + // load content only once + if (controlContent.empty() == false) + return true; + + std::ostringstream content; + if (debDebPkgFileIndex::GetContent(content, debFileName) == false) + return false; + // add two newlines to make sure the scanner finds the section, + // which is usually done by pkgTagFile automatically if needed. + content << "\n\n"; + + controlContent = content.str(); + if (Section.Scan(controlContent.c_str(), controlContent.length()) == false) + return _error->Error(_("Unable to parse package file %s (%d)"), debFileName.c_str(), 3); + return true; +} +bool debDebFileRecordParser::Jump(pkgCache::VerFileIterator const &) { return LoadContent(); } +bool debDebFileRecordParser::Jump(pkgCache::DescFileIterator const &) { return LoadContent(); } +std::string debDebFileRecordParser::FileName() { return debFileName; } + +debDebFileRecordParser::debDebFileRecordParser(std::string FileName) : debRecordParserBase(), d(NULL), debFileName(FileName) {} +debDebFileRecordParser::~debDebFileRecordParser() {} diff --git a/apt-pkg/deb/debrecords.h b/apt-pkg/deb/debrecords.h new file mode 100644 index 0000000..7c3b902 --- /dev/null +++ b/apt-pkg/deb/debrecords.h @@ -0,0 +1,92 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Debian Package Records - Parser for debian package records + + This provides display-type parsing for the Packages file. This is + different than the list parser which provides cache generation + services. There should be no overlap between these two. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_DEBRECORDS_H +#define PKGLIB_DEBRECORDS_H + +#include <apt-pkg/fileutl.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/tagfile.h> + +#include <string> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/indexfile.h> +#endif + +class APT_HIDDEN debRecordParserBase : public pkgRecords::Parser +{ + void * const d; + protected: + pkgTagSection Section; + + public: + // These refer to the archive file for the Version + virtual std::string FileName() APT_OVERRIDE; + virtual std::string SourcePkg() APT_OVERRIDE; + virtual std::string SourceVer() APT_OVERRIDE; + + virtual HashStringList Hashes() const APT_OVERRIDE; + + // These are some general stats about the package + virtual std::string Maintainer() APT_OVERRIDE; + virtual std::string ShortDesc(std::string const &lang) APT_OVERRIDE; + virtual std::string LongDesc(std::string const &lang) APT_OVERRIDE; + virtual std::string Name() APT_OVERRIDE; + virtual std::string Homepage() APT_OVERRIDE; + + // An arbitrary custom field + virtual std::string RecordField(const char *fieldName) APT_OVERRIDE; + + virtual void GetRec(const char *&Start,const char *&Stop) APT_OVERRIDE; + + debRecordParserBase(); + virtual ~debRecordParserBase(); +}; + +class APT_HIDDEN debRecordParser : public debRecordParserBase +{ + void * const d; + protected: + FileFd File; + pkgTagFile Tags; + + virtual bool Jump(pkgCache::VerFileIterator const &Ver) APT_OVERRIDE; + virtual bool Jump(pkgCache::DescFileIterator const &Desc) APT_OVERRIDE; + + public: + debRecordParser(std::string FileName,pkgCache &Cache); + virtual ~debRecordParser(); +}; + +// custom record parser that reads deb files directly +class APT_HIDDEN debDebFileRecordParser : public debRecordParserBase +{ + void * const d; + std::string debFileName; + std::string controlContent; + + APT_HIDDEN bool LoadContent(); + protected: + // single file files, so no jumping whatsoever + bool Jump(pkgCache::VerFileIterator const &) APT_OVERRIDE; + bool Jump(pkgCache::DescFileIterator const &) APT_OVERRIDE; + + public: + virtual std::string FileName() APT_OVERRIDE; + + debDebFileRecordParser(std::string FileName); + virtual ~debDebFileRecordParser(); +}; + +#endif diff --git a/apt-pkg/deb/debsrcrecords.cc b/apt-pkg/deb/debsrcrecords.cc new file mode 100644 index 0000000..9656fca --- /dev/null +++ b/apt-pkg/deb/debsrcrecords.cc @@ -0,0 +1,324 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Debian Source Package Records - Parser implementation for Debian style + source indexes + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/deblistparser.h> +#include <apt-pkg/debsrcrecords.h> +#include <apt-pkg/error.h> +#include <apt-pkg/gpgv.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/srcrecords.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile-keys.h> +#include <apt-pkg/tagfile.h> + +#include <algorithm> +#include <string> +#include <vector> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + /*}}}*/ + +using std::max; +using std::string; + +debSrcRecordParser::debSrcRecordParser(std::string const &File,pkgIndexFile const *Index) + : Parser(Index), d(NULL), Tags(&Fd), iOffset(0), Buffer(NULL) +{ + if (File.empty() == false) + { + if (Fd.Open(File, FileFd::ReadOnly, FileFd::Extension)) + Tags.Init(&Fd, 102400); + } +} +std::string debSrcRecordParser::Package() const /*{{{*/ +{ + auto const name = Sect.Find(pkgTagSection::Key::Package); + if (iIndex != nullptr || name.empty() == false) + return name.to_string(); + return Sect.Find(pkgTagSection::Key::Source).to_string(); +} + /*}}}*/ +// SrcRecordParser::Binaries - Return the binaries field /*{{{*/ +// --------------------------------------------------------------------- +/* This member parses the binaries field into a pair of class arrays and + returns a list of strings representing all of the components of the + binaries field. The returned array need not be freed and will be + reused by the next Binaries function call. This function is commonly + used during scanning to find the right package */ +const char **debSrcRecordParser::Binaries() +{ + const char *Start, *End; + if (Sect.Find(pkgTagSection::Key::Binary, Start, End) == false) + return NULL; + for (; isspace_ascii(*Start) != 0; ++Start); + if (Start >= End) + return NULL; + + StaticBinList.clear(); + free(Buffer); + Buffer = strndup(Start, End - Start); + + char* bin = Buffer; + do { + char* binStartNext = strchrnul(bin, ','); + // Found a comma, clean up any space before it + if (binStartNext > Buffer) { + char* binEnd = binStartNext - 1; + for (; binEnd > Buffer && isspace_ascii(*binEnd) != 0; --binEnd) + *binEnd = 0; + } + StaticBinList.push_back(bin); + if (*binStartNext != ',') + break; + *binStartNext = '\0'; + for (bin = binStartNext + 1; isspace_ascii(*bin) != 0; ++bin) + ; + } while (*bin != '\0'); + StaticBinList.push_back(NULL); + + return &StaticBinList[0]; +} + /*}}}*/ +// SrcRecordParser::BuildDepends - Return the Build-Depends information /*{{{*/ +// --------------------------------------------------------------------- +/* This member parses the build-depends information and returns a list of + package/version records representing the build dependency. The returned + array need not be freed and will be reused by the next call to this + function */ +bool debSrcRecordParser::BuildDepends(std::vector<pkgSrcRecords::Parser::BuildDepRec> &BuildDeps, + bool const &ArchOnly, bool const &StripMultiArch) +{ + BuildDeps.clear(); + + pkgTagSection::Key const fields[] = { + pkgTagSection::Key::Build_Depends, + pkgTagSection::Key::Build_Depends_Indep, + pkgTagSection::Key::Build_Conflicts, + pkgTagSection::Key::Build_Conflicts_Indep, + pkgTagSection::Key::Build_Depends_Arch, + pkgTagSection::Key::Build_Conflicts_Arch, + }; + for (unsigned short I = 0; I < sizeof(fields) / sizeof(fields[0]); ++I) + { + if (ArchOnly && (fields[I] == pkgTagSection::Key::Build_Depends_Indep || fields[I] == pkgTagSection::Key::Build_Conflicts_Indep)) + continue; + + const char *Start, *Stop; + if (Sect.Find(fields[I], Start, Stop) == false) + continue; + + if (Start == Stop) + continue; + + while (1) + { + BuildDepRec rec; + Start = debListParser::ParseDepends(Start, Stop, + rec.Package, rec.Version, rec.Op, true, StripMultiArch, true); + + if (Start == 0) + return _error->Error("Problem parsing dependency: %s", BuildDepType(I)); + rec.Type = I; + + // 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; + } + } + + return true; +} + /*}}}*/ +// SrcRecordParser::Files - Return a list of files for this source /*{{{*/ +// --------------------------------------------------------------------- +/* This parses the list of files and returns it, each file is required to have + a complete source package */ +bool debSrcRecordParser::Files(std::vector<pkgSrcRecords::File> &F) +{ + std::vector<pkgSrcRecords::File2> F2; + if (Files2(F2) == false) + return false; + for (std::vector<pkgSrcRecords::File2>::const_iterator f2 = F2.begin(); f2 != F2.end(); ++f2) + { + pkgSrcRecords::File2 f; +#if __GNUC__ >= 4 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + f.MD5Hash = f2->MD5Hash; + f.Size = f2->Size; +#if __GNUC__ >= 4 + #pragma GCC diagnostic pop +#endif + f.Path = f2->Path; + f.Type = f2->Type; + F.push_back(f); + } + return true; +} +bool debSrcRecordParser::Files2(std::vector<pkgSrcRecords::File2> &List) +{ + List.clear(); + + // Stash the / terminated directory prefix + std::string Base = Sect.Find(pkgTagSection::Key::Directory).to_string(); + if (Base.empty() == false && Base[Base.length()-1] != '/') + Base += '/'; + + std::vector<std::string> const compExts = APT::Configuration::getCompressorExtensions(); + + for (char const * const * type = HashString::SupportedHashes(); *type != NULL; ++type) + { + // derive field from checksum type + std::string checksumField("Checksums-"); + if (strcmp(*type, "MD5Sum") == 0) + checksumField = "Files"; // historic name for MD5 checksums + else + checksumField.append(*type); + + string const Files = Sect.FindS(checksumField.c_str()); + if (Files.empty() == true) + continue; + + // Iterate over the entire list grabbing each triplet + const char *C = Files.c_str(); + while (*C != 0) + { + string hash, size, path; + + // Parse each of the elements + if (ParseQuoteWord(C, hash) == false || + ParseQuoteWord(C, size) == false || + ParseQuoteWord(C, path) == false) + return _error->Error("Error parsing file record in %s of source package %s", checksumField.c_str(), Package().c_str()); + + if (iIndex == nullptr && checksumField == "Files") + { + // the Files field has a different format than the rest in deb-changes files + std::string ignore; + if (ParseQuoteWord(C, ignore) == false || + ParseQuoteWord(C, path) == false) + return _error->Error("Error parsing file record in %s of source package %s", checksumField.c_str(), Package().c_str()); + } + + HashString const hashString(*type, hash); + if (Base.empty() == false) + path = Base + path; + + // look if we have a record for this file already + std::vector<pkgSrcRecords::File2>::iterator file = List.begin(); + for (; file != List.end(); ++file) + if (file->Path == path) + break; + + // we have it already, store the new hash and be done + if (file != List.end()) + { + if (checksumField == "Files") + { + APT_IGNORE_DEPRECATED_PUSH + file->MD5Hash = hash; + APT_IGNORE_DEPRECATED_POP + } + // an error here indicates that we have two different hashes for the same file + if (file->Hashes.push_back(hashString) == false) + return _error->Error("Error parsing checksum in %s of source package %s", checksumField.c_str(), Package().c_str()); + continue; + } + + // we haven't seen this file yet + pkgSrcRecords::File2 F; + F.Path = path; + F.FileSize = strtoull(size.c_str(), NULL, 10); + F.Hashes.push_back(hashString); + F.Hashes.FileSize(F.FileSize); + + APT_IGNORE_DEPRECATED_PUSH + F.Size = F.FileSize; + if (checksumField == "Files") + F.MD5Hash = hash; + APT_IGNORE_DEPRECATED_POP + + // Try to guess what sort of file it is we are getting. + string::size_type Pos = F.Path.length()-1; + while (1) + { + string::size_type Tmp = F.Path.rfind('.',Pos); + if (Tmp == string::npos) + break; + if (F.Type == "tar") { + // source v3 has extension 'debian.tar.*' instead of 'diff.*' + if (string(F.Path, Tmp+1, Pos-Tmp) == "debian") + F.Type = "diff"; + break; + } + F.Type = string(F.Path,Tmp+1,Pos-Tmp); + + if (std::find(compExts.begin(), compExts.end(), std::string(".").append(F.Type)) != compExts.end() || + F.Type == "tar") + { + Pos = Tmp-1; + continue; + } + + break; + } + List.push_back(F); + } + } + + return true; +} + /*}}}*/ +// SrcRecordParser::~SrcRecordParser - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +debSrcRecordParser::~debSrcRecordParser() +{ + // was allocated via strndup() + free(Buffer); +} + /*}}}*/ + + +debDscRecordParser::debDscRecordParser(std::string const &DscFile, pkgIndexFile const *Index) + : debSrcRecordParser("", Index) +{ + // support clear signed files + if (OpenMaybeClearSignedFile(DscFile, Fd) == false) + { + _error->Error("Failed to open %s", DscFile.c_str()); + return; + } + + // re-init to ensure the updated Fd is used + Tags.Init(&Fd, pkgTagFile::SUPPORT_COMMENTS); + // read the first (and only) record + Step(); + +} diff --git a/apt-pkg/deb/debsrcrecords.h b/apt-pkg/deb/debsrcrecords.h new file mode 100644 index 0000000..45617f6 --- /dev/null +++ b/apt-pkg/deb/debsrcrecords.h @@ -0,0 +1,68 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Debian Source Package Records - Parser implementation for Debian style + source indexes + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_DEBSRCRECORDS_H +#define PKGLIB_DEBSRCRECORDS_H + +#include <apt-pkg/fileutl.h> +#include <apt-pkg/srcrecords.h> +#include <apt-pkg/tagfile.h> + +#include <string> +#include <vector> +#include <stddef.h> + +class pkgIndexFile; + +class APT_HIDDEN debSrcRecordParser : public pkgSrcRecords::Parser +{ + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + protected: + FileFd Fd; + pkgTagFile Tags; + pkgTagSection Sect; + std::vector<const char*> StaticBinList; + unsigned long iOffset; + char *Buffer; + + public: + + virtual bool Restart() APT_OVERRIDE {return Jump(0);}; + virtual bool Step() APT_OVERRIDE {iOffset = Tags.Offset(); return Tags.Step(Sect);}; + virtual bool Jump(unsigned long const &Off) APT_OVERRIDE {iOffset = Off; return Tags.Jump(Sect,Off);}; + + virtual std::string Package() const APT_OVERRIDE; + virtual std::string Version() const APT_OVERRIDE {return Sect.FindS("Version");}; + virtual std::string Maintainer() const APT_OVERRIDE {return Sect.FindS("Maintainer");}; + virtual std::string Section() const APT_OVERRIDE {return Sect.FindS("Section");}; + virtual const char **Binaries() APT_OVERRIDE; + virtual bool BuildDepends(std::vector<BuildDepRec> &BuildDeps, bool const &ArchOnly, bool const &StripMultiArch = true) APT_OVERRIDE; + virtual unsigned long Offset() APT_OVERRIDE {return iOffset;}; + virtual std::string AsStr() APT_OVERRIDE + { + const char *Start=0,*Stop=0; + Sect.GetSection(Start,Stop); + return std::string(Start,Stop); + }; + virtual bool Files(std::vector<pkgSrcRecords::File> &F) APT_OVERRIDE; + bool Files2(std::vector<pkgSrcRecords::File2> &F); + + debSrcRecordParser(std::string const &File,pkgIndexFile const *Index); + virtual ~debSrcRecordParser(); +}; + +class APT_HIDDEN debDscRecordParser : public debSrcRecordParser +{ + public: + debDscRecordParser(std::string const &DscFile, pkgIndexFile const *Index); +}; + +#endif diff --git a/apt-pkg/deb/debsystem.cc b/apt-pkg/deb/debsystem.cc new file mode 100644 index 0000000..0c7e9ff --- /dev/null +++ b/apt-pkg/deb/debsystem.cc @@ -0,0 +1,485 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + System - Abstraction for running on different systems. + + Basic general structure.. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/debindexfile.h> +#include <apt-pkg/debsystem.h> +#include <apt-pkg/debversion.h> +#include <apt-pkg/dpkgpm.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/pkgcache.h> + +#include <algorithm> + +#include <string> +#include <vector> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ + +using std::string; + +debSystem debSys; + +class APT_HIDDEN debSystemPrivate { +public: + debSystemPrivate() : FrontendLockFD(-1), LockFD(-1), LockCount(0), StatusFile(0) + { + } + // For locking support + int FrontendLockFD; + int LockFD; + unsigned LockCount; + + debStatusIndex *StatusFile; +}; + +// System::debSystem - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +debSystem::debSystem() : pkgSystem("Debian dpkg interface", &debVS), d(new debSystemPrivate()) +{ +} + /*}}}*/ +// System::~debSystem - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +debSystem::~debSystem() +{ + delete d->StatusFile; + delete d; +} + /*}}}*/ +// System::Lock - Get the lock /*{{{*/ +// --------------------------------------------------------------------- +/* This mirrors the operations dpkg does when it starts up. Note the + checking of the updates directory. */ +bool debSystem::Lock() +{ + // Disable file locking + if (_config->FindB("Debug::NoLocking",false) == true || d->LockCount > 0) + { + d->LockCount++; + return true; + } + + // Create the lockfile + string AdminDir = flNotFile(_config->FindFile("Dir::State::status")); + string FrontendLockFile = AdminDir + "lock-frontend"; + d->FrontendLockFD = GetLock(FrontendLockFile); + if (d->FrontendLockFD == -1) + { + if (errno == EACCES || errno == EAGAIN) + return _error->Error(_("Unable to acquire the dpkg frontend lock (%s), " + "is another process using it?"),FrontendLockFile.c_str()); + else + return _error->Error(_("Unable to acquire the dpkg frontend lock (%s), " + "are you root?"),FrontendLockFile.c_str()); + } + if (LockInner() == false) + { + close(d->FrontendLockFD); + return false; + } + + // See if we need to abort with a dirty journal + if (CheckUpdates() == true) + { + close(d->LockFD); + close(d->FrontendLockFD); + d->FrontendLockFD = -1; + d->LockFD = -1; + const char *cmd; + if (getenv("SUDO_USER") != NULL) + cmd = "sudo dpkg --configure -a"; + else + cmd = "dpkg --configure -a"; + // TRANSLATORS: the %s contains the recovery command, usually + // dpkg --configure -a + return _error->Error(_("dpkg was interrupted, you must manually " + "run '%s' to correct the problem. "), cmd); + } + + d->LockCount++; + + return true; +} + +bool debSystem::LockInner() { + string AdminDir = flNotFile(_config->FindFile("Dir::State::status")); + d->LockFD = GetLock(AdminDir + "lock"); + if (d->LockFD == -1) + { + if (errno == EACCES || errno == EAGAIN) + return _error->Error(_("Unable to lock the administration directory (%s), " + "is another process using it?"),AdminDir.c_str()); + else + return _error->Error(_("Unable to lock the administration directory (%s), " + "are you root?"),AdminDir.c_str()); + } + return true; +} + /*}}}*/ +// System::UnLock - Drop a lock /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool debSystem::UnLock(bool NoErrors) +{ + if (d->LockCount == 0 && NoErrors == true) + return false; + + if (d->LockCount < 1) + return _error->Error(_("Not locked")); + if (--d->LockCount == 0) + { + close(d->LockFD); + close(d->FrontendLockFD); + d->LockCount = 0; + } + + return true; +} +bool debSystem::UnLockInner(bool NoErrors) { + (void) NoErrors; + close(d->LockFD); + return true; +} + /*}}}*/ +// System::IsLocked - Check if system is locked /*{{{*/ +// --------------------------------------------------------------------- +/* This checks if the frontend lock is hold. The inner lock might be + * released. */ +bool debSystem::IsLocked() +{ + return d->LockCount > 0; +} + /*}}}*/ +// System::CheckUpdates - Check if the updates dir is dirty /*{{{*/ +// --------------------------------------------------------------------- +/* This does a check of the updates directory (dpkg journal) to see if it has + any entries in it. */ +bool debSystem::CheckUpdates() +{ + // Check for updates.. (dirty) + string File = flNotFile(_config->FindFile("Dir::State::status")) + "updates/"; + DIR *DirP = opendir(File.c_str()); + if (DirP == 0) + return false; + + /* We ignore any files that are not all digits, this skips .,.. and + some tmp files dpkg will leave behind.. */ + bool Damaged = false; + for (struct dirent *Ent = readdir(DirP); Ent != 0; Ent = readdir(DirP)) + { + Damaged = true; + for (unsigned int I = 0; Ent->d_name[I] != 0; I++) + { + // Check if its not a digit.. + if (isdigit(Ent->d_name[I]) == 0) + { + Damaged = false; + break; + } + } + if (Damaged == true) + break; + } + closedir(DirP); + + return Damaged; +} + /*}}}*/ +// System::CreatePM - Create the underlying package manager /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgPackageManager *debSystem::CreatePM(pkgDepCache *Cache) const +{ + return new pkgDPkgPM(Cache); +} + /*}}}*/ +// System::Initialize - Setup the configuration space.. /*{{{*/ +// --------------------------------------------------------------------- +/* These are the Debian specific configuration variables.. */ +static std::string getDpkgStatusLocation(Configuration const &Cnf) { + Configuration PathCnf; + PathCnf.Set("Dir", Cnf.Find("Dir", "/")); + PathCnf.Set("Dir::State::status", "status"); + auto const cnfstatedir = Cnf.Find("Dir::State", STATE_DIR + 1); + // if the state dir ends in apt, replace it with dpkg - + // for the default this gives us the same as the fallback below. + // This can't be a ../dpkg as that would play bad with symlinks + std::string statedir; + if (APT::String::Endswith(cnfstatedir, "/apt/")) + statedir.assign(cnfstatedir, 0, cnfstatedir.length() - 5); + else if (APT::String::Endswith(cnfstatedir, "/apt")) + statedir.assign(cnfstatedir, 0, cnfstatedir.length() - 4); + if (statedir.empty()) + PathCnf.Set("Dir::State", "var/lib/dpkg"); + else + PathCnf.Set("Dir::State", flCombine(statedir, "dpkg")); + return PathCnf.FindFile("Dir::State::status"); +} +bool debSystem::Initialize(Configuration &Cnf) +{ + /* These really should be jammed into a generic 'Local Database' engine + which is yet to be determined. The functions in pkgcachegen should + be the only users of these */ + Cnf.CndSet("Dir::State::extended_states", "extended_states"); + if (Cnf.Exists("Dir::State::status") == false) + Cnf.Set("Dir::State::status", getDpkgStatusLocation(Cnf)); + Cnf.CndSet("Dir::Bin::dpkg",BIN_DIR"/dpkg"); + + if (d->StatusFile) { + delete d->StatusFile; + d->StatusFile = 0; + } + + return true; +} + /*}}}*/ +// System::ArchiveSupported - Is a file format supported /*{{{*/ +// --------------------------------------------------------------------- +/* The standard name for a deb is 'deb'.. There are no separate versions + of .deb to worry about.. */ +APT_PURE bool debSystem::ArchiveSupported(const char *Type) +{ + if (strcmp(Type,"deb") == 0) + return true; + return false; +} + /*}}}*/ +// System::Score - Determine how 'Debiany' this sys is.. /*{{{*/ +// --------------------------------------------------------------------- +/* We check some files that are sure tell signs of this being a Debian + System.. */ +signed debSystem::Score(Configuration const &Cnf) +{ + signed Score = 0; + if (FileExists(Cnf.FindFile("Dir::State::status",getDpkgStatusLocation(Cnf).c_str())) == true) + Score += 10; + if (FileExists(Cnf.Find("Dir::Bin::dpkg",BIN_DIR"/dpkg")) == true) + Score += 10; + if (FileExists("/etc/debian_version") == true) + Score += 10; + return Score; +} + /*}}}*/ +// System::AddStatusFiles - Register the status files /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool debSystem::AddStatusFiles(std::vector<pkgIndexFile *> &List) +{ + if (d->StatusFile == 0) + d->StatusFile = new debStatusIndex(_config->FindFile("Dir::State::status")); + List.push_back(d->StatusFile); + return true; +} + /*}}}*/ +// System::FindIndex - Get an index file for status files /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool debSystem::FindIndex(pkgCache::PkgFileIterator File, + pkgIndexFile *&Found) const +{ + if (d->StatusFile == 0) + return false; + if (d->StatusFile->FindInCache(*File.Cache()) == File) + { + Found = d->StatusFile; + return true; + } + + return false; +} + /*}}}*/ + +std::string debSystem::GetDpkgExecutable() /*{{{*/ +{ + string Tmp = _config->Find("Dir::Bin::dpkg","dpkg"); + string const dpkgChrootDir = _config->FindDir("DPkg::Chroot-Directory", "/"); + size_t dpkgChrootLen = dpkgChrootDir.length(); + if (dpkgChrootDir != "/" && Tmp.find(dpkgChrootDir) == 0) + { + if (dpkgChrootDir[dpkgChrootLen - 1] == '/') + --dpkgChrootLen; + Tmp = Tmp.substr(dpkgChrootLen); + } + return Tmp; +} + /*}}}*/ +std::vector<std::string> debSystem::GetDpkgBaseCommand() /*{{{*/ +{ + // Generate the base argument list for dpkg + std::vector<std::string> Args = { GetDpkgExecutable() }; + // Stick in any custom dpkg options + Configuration::Item const *Opts = _config->Tree("DPkg::Options"); + if (Opts != 0) + { + Opts = Opts->Child; + for (; Opts != 0; Opts = Opts->Next) + { + if (Opts->Value.empty() == true) + continue; + Args.push_back(Opts->Value); + } + } + return Args; +} + /*}}}*/ +void debSystem::DpkgChrootDirectory() /*{{{*/ +{ + std::string const chrootDir = _config->FindDir("DPkg::Chroot-Directory"); + if (chrootDir == "/") + return; + std::cerr << "Chrooting into " << chrootDir << std::endl; + if (chroot(chrootDir.c_str()) != 0) + _exit(100); + if (chdir("/") != 0) + _exit(100); +} + /*}}}*/ +pid_t debSystem::ExecDpkg(std::vector<std::string> const &sArgs, int * const inputFd, int * const outputFd, bool const DiscardOutput)/*{{{*/ +{ + std::vector<const char *> Args(sArgs.size(), NULL); + std::transform(sArgs.begin(), sArgs.end(), Args.begin(), [](std::string const &s) { return s.c_str(); }); + Args.push_back(NULL); + + int external[2] = {-1, -1}; + if (inputFd != nullptr || outputFd != nullptr) + if (pipe(external) != 0) + { + _error->WarningE("dpkg", "Can't create IPC pipe for dpkg call"); + return -1; + } + + pid_t const dpkg = ExecFork(); + if (dpkg == 0) { + int const nullfd = open("/dev/null", O_RDONLY); + if (inputFd == nullptr) + dup2(nullfd, STDIN_FILENO); + else + { + close(external[1]); + dup2(external[0], STDIN_FILENO); + } + if (outputFd == nullptr) + dup2(nullfd, STDOUT_FILENO); + else + { + close(external[0]); + dup2(external[1], STDOUT_FILENO); + } + if (DiscardOutput == true) + dup2(nullfd, STDERR_FILENO); + debSystem::DpkgChrootDirectory(); + + if (_system != nullptr && _system->IsLocked() == true) + { + setenv("DPKG_FRONTEND_LOCKED", "true", 1); + } + + if (_config->Find("DPkg::Path", "").empty() == false) + setenv("PATH", _config->Find("DPkg::Path", "").c_str(), 1); + + execvp(Args[0], (char**) &Args[0]); + _error->WarningE("dpkg", "Can't execute dpkg!"); + _exit(100); + } + if (outputFd != nullptr) + { + close(external[1]); + *outputFd = external[0]; + } + else if (inputFd != nullptr) + { + close(external[0]); + *inputFd = external[1]; + } + return dpkg; +} + /*}}}*/ +bool debSystem::SupportsMultiArch() /*{{{*/ +{ + std::vector<std::string> Args = GetDpkgBaseCommand(); + Args.push_back("--assert-multi-arch"); + pid_t const dpkgAssertMultiArch = ExecDpkg(Args, nullptr, nullptr, true); + if (dpkgAssertMultiArch > 0) + { + int Status = 0; + while (waitpid(dpkgAssertMultiArch, &Status, 0) != dpkgAssertMultiArch) + { + if (errno == EINTR) + continue; + _error->WarningE("dpkgGo", _("Waited for %s but it wasn't there"), "dpkg --assert-multi-arch"); + break; + } + if (WIFEXITED(Status) == true && WEXITSTATUS(Status) == 0) + return true; + } + return false; +} + /*}}}*/ +std::vector<std::string> debSystem::SupportedArchitectures() /*{{{*/ +{ + std::vector<std::string> archs; + { + string const arch = _config->Find("APT::Architecture"); + if (arch.empty() == false) + archs.push_back(std::move(arch)); + } + + std::vector<std::string> sArgs = GetDpkgBaseCommand(); + sArgs.push_back("--print-foreign-architectures"); + int outputFd = -1; + pid_t const dpkgMultiArch = ExecDpkg(sArgs, nullptr, &outputFd, true); + if (dpkgMultiArch == -1) + return archs; + + FILE *dpkg = fdopen(outputFd, "r"); + if(dpkg != NULL) { + char* buf = NULL; + size_t bufsize = 0; + while (getline(&buf, &bufsize, dpkg) != -1) + { + char* tok_saveptr; + char* arch = strtok_r(buf, " ", &tok_saveptr); + while (arch != NULL) { + for (; isspace_ascii(*arch) != 0; ++arch); + if (arch[0] != '\0') { + char const* archend = arch; + for (; isspace_ascii(*archend) == 0 && *archend != '\0'; ++archend); + string a(arch, (archend - arch)); + if (std::find(archs.begin(), archs.end(), a) == archs.end()) + archs.push_back(a); + } + arch = strtok_r(NULL, " ", &tok_saveptr); + } + } + free(buf); + fclose(dpkg); + } + ExecWait(dpkgMultiArch, "dpkg --print-foreign-architectures", true); + return archs; +} + /*}}}*/ diff --git a/apt-pkg/deb/debsystem.h b/apt-pkg/deb/debsystem.h new file mode 100644 index 0000000..4503676 --- /dev/null +++ b/apt-pkg/deb/debsystem.h @@ -0,0 +1,61 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + System - Debian version of the System Class + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_DEBSYSTEM_H +#define PKGLIB_DEBSYSTEM_H + +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgsystem.h> + +#include <vector> +class Configuration; +class pkgIndexFile; +class pkgPackageManager; +class debSystemPrivate; +class pkgDepCache; + +#ifndef APT_10_CLEANER_HEADERS +class debStatusIndex; +#endif + +class debSystem : public pkgSystem +{ + // private d-pointer + debSystemPrivate * const d; + APT_HIDDEN bool CheckUpdates(); + + public: + + virtual bool Lock() APT_OVERRIDE; + virtual bool UnLock(bool NoErrors = false) APT_OVERRIDE; + virtual pkgPackageManager *CreatePM(pkgDepCache *Cache) const APT_OVERRIDE; + virtual bool Initialize(Configuration &Cnf) APT_OVERRIDE; + virtual bool ArchiveSupported(const char *Type) APT_OVERRIDE; + virtual signed Score(Configuration const &Cnf) APT_OVERRIDE; + virtual bool AddStatusFiles(std::vector<pkgIndexFile *> &List) APT_OVERRIDE; + virtual bool FindIndex(pkgCache::PkgFileIterator File, + pkgIndexFile *&Found) const APT_OVERRIDE; + + debSystem(); + virtual ~debSystem(); + + APT_HIDDEN static std::string GetDpkgExecutable(); + APT_HIDDEN static std::vector<std::string> GetDpkgBaseCommand(); + APT_HIDDEN static void DpkgChrootDirectory(); + APT_HIDDEN static pid_t ExecDpkg(std::vector<std::string> const &sArgs, int * const inputFd, int * const outputFd, bool const DiscardOutput); + APT_HIDDEN static bool SupportsMultiArch(); + APT_HIDDEN static std::vector<std::string> SupportedArchitectures(); + + APT_HIDDEN bool LockInner(); + APT_HIDDEN bool UnLockInner(bool NoErrors=false); + APT_HIDDEN bool IsLocked(); +}; + +extern debSystem debSys; + +#endif diff --git a/apt-pkg/deb/debversion.cc b/apt-pkg/deb/debversion.cc new file mode 100644 index 0000000..d2ebd28 --- /dev/null +++ b/apt-pkg/deb/debversion.cc @@ -0,0 +1,279 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Debian Version - Versioning system for Debian + + This implements the standard Debian versioning system. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/debversion.h> +#include <apt-pkg/pkgcache.h> + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + /*}}}*/ + +debVersioningSystem debVS; + +// debVS::debVersioningSystem - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +debVersioningSystem::debVersioningSystem() +{ + Label = "Standard .deb"; +} + /*}}}*/ + +// debVS::CmpFragment - Compare versions /*{{{*/ +// --------------------------------------------------------------------- +/* This compares a fragment of the version. This is a slightly adapted + version of what dpkg uses in dpkg/lib/dpkg/version.c. + In particular, the a | b = NULL check is removed as we check this in the + caller, we use an explicit end for a | b strings and we check ~ explicit. */ +static int order(char c) +{ + if (isdigit(c)) + return 0; + else if (isalpha(c)) + return c; + else if (c == '~') + return -1; + else if (c) + return c + 256; + else + return 0; +} +int debVersioningSystem::CmpFragment(const char *A,const char *AEnd, + const char *B,const char *BEnd) +{ + /* Iterate over the whole string + What this does is to split the whole string into groups of + numeric and non numeric portions. For instance: + a67bhgs89 + Has 4 portions 'a', '67', 'bhgs', '89'. A more normal: + 2.7.2-linux-1 + Has '2', '.', '7', '.' ,'-linux-','1' */ + const char *lhs = A; + const char *rhs = B; + while (lhs != AEnd && rhs != BEnd) + { + int first_diff = 0; + + while (lhs != AEnd && rhs != BEnd && + (!isdigit(*lhs) || !isdigit(*rhs))) + { + int vc = order(*lhs); + int rc = order(*rhs); + if (vc != rc) + return vc - rc; + ++lhs; ++rhs; + } + + while (*lhs == '0') + ++lhs; + while (*rhs == '0') + ++rhs; + while (isdigit(*lhs) && isdigit(*rhs)) + { + if (!first_diff) + first_diff = *lhs - *rhs; + ++lhs; + ++rhs; + } + + if (isdigit(*lhs)) + return 1; + if (isdigit(*rhs)) + return -1; + if (first_diff) + return first_diff; + } + + // The strings must be equal + if (lhs == AEnd && rhs == BEnd) + return 0; + + // lhs is shorter + if (lhs == AEnd) + { + if (*rhs == '~') return 1; + return -1; + } + + // rhs is shorter + if (rhs == BEnd) + { + if (*lhs == '~') return -1; + return 1; + } + + // Shouldn't happen + return 1; +} + /*}}}*/ +// debVS::CmpVersion - Comparison for versions /*{{{*/ +// --------------------------------------------------------------------- +/* This fragments the version into E:V-R triples and compares each + portion separately. */ +int debVersioningSystem::DoCmpVersion(const char *A,const char *AEnd, + const char *B,const char *BEnd) +{ + // Strip off the epoch and compare it + const char *lhs = (const char*) memchr(A, ':', AEnd - A); + const char *rhs = (const char*) memchr(B, ':', BEnd - B); + if (lhs == NULL) + lhs = A; + if (rhs == NULL) + rhs = B; + + // Special case: a zero epoch is the same as no epoch, + // so remove it. + if (lhs != A) + { + for (; *A == '0'; ++A); + if (A == lhs) + { + ++A; + ++lhs; + } + } + if (rhs != B) + { + for (; *B == '0'; ++B); + if (B == rhs) + { + ++B; + ++rhs; + } + } + + // Compare the epoch + int Res = CmpFragment(A,lhs,B,rhs); + if (Res != 0) + return Res; + + // Skip the : + if (lhs != A) + lhs++; + if (rhs != B) + rhs++; + + // Find the last - + const char *dlhs = (const char*) memrchr(lhs, '-', AEnd - lhs); + const char *drhs = (const char*) memrchr(rhs, '-', BEnd - rhs); + if (dlhs == NULL) + dlhs = AEnd; + if (drhs == NULL) + drhs = BEnd; + + // Compare the main version + Res = CmpFragment(lhs,dlhs,rhs,drhs); + if (Res != 0) + return Res; + + // Skip the - + if (dlhs != lhs) + dlhs++; + if (drhs != rhs) + drhs++; + + // no debian revision need to be treated like -0 + if (*(dlhs-1) == '-' && *(drhs-1) == '-') + return CmpFragment(dlhs,AEnd,drhs,BEnd); + else if (*(dlhs-1) == '-') + { + const char* null = "0"; + return CmpFragment(dlhs,AEnd,null, null+1); + } + else if (*(drhs-1) == '-') + { + const char* null = "0"; + return CmpFragment(null, null+1, drhs, BEnd); + } + else + return 0; +} + /*}}}*/ +// debVS::CheckDep - Check a single dependency /*{{{*/ +// --------------------------------------------------------------------- +/* This simply performs the version comparison and switch based on + operator. If DepVer is 0 then we are comparing against a provides + with no version. */ +bool debVersioningSystem::CheckDep(const char *PkgVer, + int Op,const char *DepVer) +{ + if (DepVer == 0 || DepVer[0] == 0) + return true; + if (PkgVer == 0 || PkgVer[0] == 0) + return false; + Op &= 0x0F; + + // fast track for (equal) strings [by location] which are by definition equal versions + if (PkgVer == DepVer) + return Op == pkgCache::Dep::Equals || Op == pkgCache::Dep::LessEq || Op == pkgCache::Dep::GreaterEq; + + // Perform the actual comparison. + int const Res = CmpVersion(PkgVer, DepVer); + switch (Op) + { + case pkgCache::Dep::LessEq: + if (Res <= 0) + return true; + break; + + case pkgCache::Dep::GreaterEq: + if (Res >= 0) + return true; + break; + + case pkgCache::Dep::Less: + if (Res < 0) + return true; + break; + + case pkgCache::Dep::Greater: + if (Res > 0) + return true; + break; + + case pkgCache::Dep::Equals: + if (Res == 0) + return true; + break; + + case pkgCache::Dep::NotEquals: + if (Res != 0) + return true; + break; + } + + return false; +} + /*}}}*/ +// debVS::UpstreamVersion - Return the upstream version string /*{{{*/ +// --------------------------------------------------------------------- +/* This strips all the debian specific information from the version number */ +std::string debVersioningSystem::UpstreamVersion(const char *Ver) +{ + // Strip off the bit before the first colon + const char *I = Ver; + for (; *I != 0 && *I != ':'; I++); + if (*I == ':') + Ver = I + 1; + + // Chop off the trailing - + I = Ver; + unsigned Last = strlen(Ver); + for (; *I != 0; I++) + if (*I == '-') + Last = I - Ver; + + return std::string(Ver,Last); +} + /*}}}*/ diff --git a/apt-pkg/deb/debversion.h b/apt-pkg/deb/debversion.h new file mode 100644 index 0000000..db70c87 --- /dev/null +++ b/apt-pkg/deb/debversion.h @@ -0,0 +1,41 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Debian Version - Versioning system for Debian + + This implements the standard Debian versioning system. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_DEBVERSION_H +#define PKGLIB_DEBVERSION_H + +#include <apt-pkg/version.h> + +#include <string> + +class debVersioningSystem : public pkgVersioningSystem +{ + public: + + static int CmpFragment(const char *A, const char *AEnd, const char *B, + const char *BEnd) APT_PURE; + + // Compare versions.. + virtual int DoCmpVersion(const char *A,const char *Aend, + const char *B,const char *Bend) APT_OVERRIDE APT_PURE; + virtual bool CheckDep(const char *PkgVer,int Op,const char *DepVer) APT_OVERRIDE APT_PURE; + virtual APT_PURE int DoCmpReleaseVer(const char *A,const char *Aend, + const char *B,const char *Bend) APT_OVERRIDE + { + return DoCmpVersion(A,Aend,B,Bend); + } + virtual std::string UpstreamVersion(const char *A) APT_OVERRIDE; + + debVersioningSystem(); +}; + +extern debVersioningSystem debVS; + +#endif diff --git a/apt-pkg/deb/dpkgpm.cc b/apt-pkg/deb/dpkgpm.cc new file mode 100644 index 0000000..ffa880d --- /dev/null +++ b/apt-pkg/deb/dpkgpm.cc @@ -0,0 +1,2491 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + DPKG Package Manager - Provide an interface to dpkg + + ##################################################################### */ + /*}}}*/ +// Includes /*{{{*/ +#include <config.h> + +#include <apt-pkg/cachefile.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/debsystem.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/dpkgpm.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/install-progress.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/packagemanager.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/statechanges.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/version.h> + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <pwd.h> +#include <signal.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <termios.h> +#include <time.h> +#include <unistd.h> + +#include <algorithm> +#include <array> +#include <cstring> +#include <iostream> +#include <map> +#include <numeric> +#include <set> +#include <sstream> +#include <string> +#include <type_traits> +#include <unordered_set> +#include <utility> +#include <vector> + +#include <apti18n.h> + /*}}}*/ + +extern char **environ; + +using namespace std; + +APT_PURE static string AptHistoryRequestingUser() /*{{{*/ +{ + const char* EnvKeys[]{"SUDO_UID", "PKEXEC_UID", "PACKAGEKIT_CALLER_UID"}; + + for (const auto &Key: EnvKeys) + { + if (getenv(Key) != nullptr) + { + int uid = atoi(getenv(Key)); + if (uid > 0) { + struct passwd pwd; + struct passwd *result; + char buf[255]; + if (getpwuid_r(uid, &pwd, buf, sizeof(buf), &result) == 0 && result != NULL) { + std::string res; + strprintf(res, "%s (%d)", pwd.pw_name, uid); + return res; + } + } + } + } + return ""; +} + /*}}}*/ +APT_PURE static unsigned int EnvironmentSize() /*{{{*/ +{ + unsigned int size = 0; + char **envp = environ; + + while (*envp != NULL) + size += strlen (*envp++) + 1; + + return size; +} + /*}}}*/ +class pkgDPkgPMPrivate /*{{{*/ +{ +public: + pkgDPkgPMPrivate() : stdin_is_dev_null(false), status_fd_reached_end_of_file(false), + dpkgbuf_pos(0), term_out(NULL), history_out(NULL), + progress(NULL), tt_is_valid(false), master(-1), + slave(NULL), protect_slave_from_dying(-1), + direct_stdin(false) + { + dpkgbuf[0] = '\0'; + } + ~pkgDPkgPMPrivate() + { + } + bool stdin_is_dev_null; + bool status_fd_reached_end_of_file; + // the buffer we use for the dpkg status-fd reading + char dpkgbuf[1024]; + size_t dpkgbuf_pos; + FILE *term_out; + FILE *history_out; + string dpkg_error; + APT::Progress::PackageManager *progress; + + // pty stuff + struct termios tt; + bool tt_is_valid; + int master; + char * slave; + int protect_slave_from_dying; + + // signals + sigset_t sigmask; + sigset_t original_sigmask; + + bool direct_stdin; +}; + /*}}}*/ +namespace +{ + // Maps the dpkg "processing" info to human readable names. Entry 0 + // of each array is the key, entry 1 is the value. + const std::pair<const char *, const char *> PackageProcessingOps[] = { + std::make_pair("install", N_("Preparing %s")), + // we don't care for the difference + std::make_pair("upgrade", N_("Preparing %s")), + std::make_pair("configure", N_("Preparing to configure %s")), + std::make_pair("remove", N_("Preparing for removal of %s")), + std::make_pair("purge", N_("Preparing to completely remove %s")), + std::make_pair("disappear", N_("Noting disappearance of %s")), + std::make_pair("trigproc", N_("Running post-installation trigger %s")) + }; + + const std::pair<const char *, const char *> * const PackageProcessingOpsBegin = PackageProcessingOps; + const std::pair<const char *, const char *> * const PackageProcessingOpsEnd = PackageProcessingOps + sizeof(PackageProcessingOps) / sizeof(PackageProcessingOps[0]); + + // Predicate to test whether an entry in the PackageProcessingOps + // array matches a string. + class MatchProcessingOp + { + const char *target; + + public: + explicit MatchProcessingOp(const char *the_target) + : target(the_target) + { + } + + bool operator()(const std::pair<const char *, const char *> &pair) const + { + return strcmp(pair.first, target) == 0; + } + }; +} + +// ionice - helper function to ionice the given PID /*{{{*/ +/* there is no C header for ionice yet - just the syscall interface + so we use the binary from util-linux */ +static bool ionice(int PID) +{ + if (!FileExists("/usr/bin/ionice")) + return false; + pid_t Process = ExecFork(); + if (Process == 0) + { + char buf[32]; + snprintf(buf, sizeof(buf), "-p%d", PID); + const char *Args[4]; + Args[0] = "/usr/bin/ionice"; + Args[1] = "-c3"; + Args[2] = buf; + Args[3] = 0; + execv(Args[0], (char **)Args); + } + return ExecWait(Process, "ionice"); +} + /*}}}*/ +// FindNowVersion - Helper to find a Version in "now" state /*{{{*/ +// --------------------------------------------------------------------- +/* This is helpful when a package is no longer installed but has residual + * config files + */ +static +pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg) +{ + pkgCache::VerIterator Ver; + for (Ver = Pkg.VersionList(); Ver.end() == false; ++Ver) + for (pkgCache::VerFileIterator Vf = Ver.FileList(); Vf.end() == false; ++Vf) + for (pkgCache::PkgFileIterator F = Vf.File(); F.end() == false; ++F) + { + if (F.Archive() != 0 && strcmp(F.Archive(), "now") == 0) + return Ver; + } + return Ver; +} + /*}}}*/ +static pkgCache::VerIterator FindToBeRemovedVersion(pkgCache::PkgIterator const &Pkg)/*{{{*/ +{ + auto const PV = Pkg.CurrentVer(); + if (PV.end() == false) + return PV; + return FindNowVersion(Pkg); +} + /*}}}*/ + +// DPkgPM::pkgDPkgPM - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) + : pkgPackageManager(Cache),d(new pkgDPkgPMPrivate()), pkgFailures(0), PackagesDone(0), PackagesTotal(0) +{ +} + /*}}}*/ +// DPkgPM::pkgDPkgPM - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgDPkgPM::~pkgDPkgPM() +{ + delete d; +} + /*}}}*/ +// DPkgPM::Install - Install a package /*{{{*/ +// --------------------------------------------------------------------- +/* Add an install operation to the sequence list */ +bool pkgDPkgPM::Install(PkgIterator Pkg,string File) +{ + if (File.empty() == true || Pkg.end() == true) + return _error->Error("Internal Error, No file name for %s",Pkg.FullName().c_str()); + + // If the filename string begins with DPkg::Chroot-Directory, return the + // substr that is within the chroot so dpkg can access it. + string const chrootdir = _config->FindDir("DPkg::Chroot-Directory","/"); + if (chrootdir != "/" && File.find(chrootdir) == 0) + { + size_t len = chrootdir.length(); + if (chrootdir.at(len - 1) == '/') + len--; + List.push_back(Item(Item::Install,Pkg,File.substr(len))); + } + else + List.push_back(Item(Item::Install,Pkg,File)); + + return true; +} + /*}}}*/ +// DPkgPM::Configure - Configure a package /*{{{*/ +// --------------------------------------------------------------------- +/* Add a configure operation to the sequence list */ +bool pkgDPkgPM::Configure(PkgIterator Pkg) +{ + if (Pkg.end() == true) + return false; + + List.push_back(Item(Item::Configure, Pkg)); + + // Use triggers for config calls if we configure "smart" + // as otherwise Pre-Depends will not be satisfied, see #526774 + if (_config->FindB("DPkg::TriggersPending", false) == true) + List.push_back(Item(Item::TriggersPending, PkgIterator())); + + return true; +} + /*}}}*/ +// DPkgPM::Remove - Remove a package /*{{{*/ +// --------------------------------------------------------------------- +/* Add a remove operation to the sequence list */ +bool pkgDPkgPM::Remove(PkgIterator Pkg,bool Purge) +{ + if (Pkg.end() == true) + return false; + + if (Purge == true) + List.push_back(Item(Item::Purge,Pkg)); + else + List.push_back(Item(Item::Remove,Pkg)); + return true; +} + /*}}}*/ +// DPkgPM::SendPkgInfo - Send info for install-pkgs hook /*{{{*/ +// --------------------------------------------------------------------- +/* This is part of the helper script communication interface, it sends + very complete information down to the other end of the pipe.*/ +bool pkgDPkgPM::SendV2Pkgs(FILE *F) +{ + return SendPkgsInfo(F, 2); +} +bool pkgDPkgPM::SendPkgsInfo(FILE * const F, unsigned int const &Version) +{ + // This version of APT supports only v3, so don't sent higher versions + if (Version <= 3) + fprintf(F,"VERSION %u\n", Version); + else + fprintf(F,"VERSION 3\n"); + + /* Write out all of the configuration directives by walking the + configuration tree */ + const Configuration::Item *Top = _config->Tree(0); + for (; Top != 0;) + { + if (Top->Value.empty() == false) + { + fprintf(F,"%s=%s\n", + QuoteString(Top->FullTag(),"=\"\n").c_str(), + QuoteString(Top->Value,"\n").c_str()); + } + + if (Top->Child != 0) + { + Top = Top->Child; + continue; + } + + while (Top != 0 && Top->Next == 0) + Top = Top->Parent; + if (Top != 0) + Top = Top->Next; + } + fprintf(F,"\n"); + + // Write out the package actions in order. + for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I) + { + if(I->Pkg.end() == true) + continue; + + pkgDepCache::StateCache &S = Cache[I->Pkg]; + + fprintf(F,"%s ",I->Pkg.Name()); + + // Current version which we are going to replace + pkgCache::VerIterator CurVer = I->Pkg.CurrentVer(); + if (CurVer.end() == true && (I->Op == Item::Remove || I->Op == Item::Purge)) + CurVer = FindNowVersion(I->Pkg); + + if (CurVer.end() == true) + { + if (Version <= 2) + fprintf(F, "- "); + else + fprintf(F, "- - none "); + } + else + { + fprintf(F, "%s ", CurVer.VerStr()); + if (Version >= 3) + fprintf(F, "%s %s ", CurVer.Arch(), CurVer.MultiArchType()); + } + + // Show the compare operator between current and install version + if (S.InstallVer != 0) + { + pkgCache::VerIterator const InstVer = S.InstVerIter(Cache); + int Comp = 2; + if (CurVer.end() == false) + Comp = InstVer.CompareVer(CurVer); + if (Comp < 0) + fprintf(F,"> "); + else if (Comp == 0) + fprintf(F,"= "); + else if (Comp > 0) + fprintf(F,"< "); + fprintf(F, "%s ", InstVer.VerStr()); + if (Version >= 3) + fprintf(F, "%s %s ", InstVer.Arch(), InstVer.MultiArchType()); + } + else + { + if (Version <= 2) + fprintf(F, "> - "); + else + fprintf(F, "> - - none "); + } + + // Show the filename/operation + if (I->Op == Item::Install) + { + // No errors here.. + if (I->File[0] != '/') + fprintf(F,"**ERROR**\n"); + else + fprintf(F,"%s\n",I->File.c_str()); + } + else if (I->Op == Item::Configure) + fprintf(F,"**CONFIGURE**\n"); + else if (I->Op == Item::Remove || + I->Op == Item::Purge) + fprintf(F,"**REMOVE**\n"); + + if (ferror(F) != 0) + return false; + } + return true; +} + /*}}}*/ +// DPkgPM::RunScriptsWithPkgs - Run scripts with package names on stdin /*{{{*/ +// --------------------------------------------------------------------- +/* This looks for a list of scripts to run from the configuration file + each one is run and is fed on standard input a list of all .deb files + that are due to be installed. */ +bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) +{ + bool result = true; + + Configuration::Item const *Opts = _config->Tree(Cnf); + if (Opts == 0 || Opts->Child == 0) + return true; + Opts = Opts->Child; + + sighandler_t old_sigpipe = signal(SIGPIPE, SIG_IGN); + sighandler_t old_sigint = signal(SIGINT, 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; + + // Determine the protocol version + string OptSec = Opts->Value; + string::size_type Pos; + if ((Pos = OptSec.find(' ')) == string::npos || Pos == 0) + Pos = OptSec.length(); + OptSec = "DPkg::Tools::Options::" + string(Opts->Value.c_str(),Pos); + + unsigned int Version = _config->FindI(OptSec+"::Version",1); + unsigned int InfoFD = _config->FindI(OptSec + "::InfoFD", STDIN_FILENO); + + // Create the pipes + std::set<int> KeepFDs; + MergeKeepFdsFromConfiguration(KeepFDs); + int Pipes[2]; + if (pipe(Pipes) != 0) { + result = _error->Errno("pipe","Failed to create IPC pipe to subprocess"); + break; + } + if (InfoFD != (unsigned)Pipes[0]) + SetCloseExec(Pipes[0],true); + else + KeepFDs.insert(Pipes[0]); + + + SetCloseExec(Pipes[1],true); + + // Purified Fork for running the script + pid_t Process = ExecFork(KeepFDs); + if (Process == 0) + { + // Setup the FDs + dup2(Pipes[0], InfoFD); + SetCloseExec(STDOUT_FILENO,false); + SetCloseExec(STDIN_FILENO,false); + SetCloseExec(STDERR_FILENO,false); + + string hookfd; + strprintf(hookfd, "%d", InfoFD); + setenv("APT_HOOK_INFO_FD", hookfd.c_str(), 1); + + if (_system != nullptr && _system->IsLocked() == true && stringcasecmp(Cnf, "DPkg::Pre-Install-Pkgs") == 0) + setenv("DPKG_FRONTEND_LOCKED", "true", 1); + + debSystem::DpkgChrootDirectory(); + const char *Args[4]; + Args[0] = "/bin/sh"; + Args[1] = "-c"; + Args[2] = Opts->Value.c_str(); + Args[3] = 0; + execv(Args[0],(char **)Args); + _exit(100); + } + close(Pipes[0]); + FILE *F = fdopen(Pipes[1],"w"); + if (F == 0) { + result = _error->Errno("fdopen","Failed to open new FD"); + break; + } + + // Feed it the filenames. + if (Version <= 1) + { + for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I) + { + // Only deal with packages to be installed from .deb + if (I->Op != Item::Install) + continue; + + // No errors here.. + if (I->File[0] != '/') + continue; + + /* Feed the filename of each package that is pending install + into the pipe. */ + fprintf(F,"%s\n",I->File.c_str()); + if (ferror(F) != 0) + break; + } + } + else + SendPkgsInfo(F, Version); + + fclose(F); + + // Clean up the sub process + if (ExecWait(Process,Opts->Value.c_str()) == false) { + result = _error->Error("Failure running script %s",Opts->Value.c_str()); + break; + } + } + signal(SIGINT, old_sigint); + signal(SIGPIPE, old_sigpipe); + signal(SIGQUIT, old_sigquit); + + return result; +} + /*}}}*/ +// DPkgPM::DoStdin - Read stdin and pass to master pty /*{{{*/ +// --------------------------------------------------------------------- +/* +*/ +void pkgDPkgPM::DoStdin(int master) +{ + unsigned char input_buf[256] = {0,}; + ssize_t len = read(STDIN_FILENO, input_buf, sizeof(input_buf)); + if (len) + FileFd::Write(master, input_buf, len); + else + d->stdin_is_dev_null = true; +} + /*}}}*/ +// DPkgPM::DoTerminalPty - Read the terminal pty and write log /*{{{*/ +// --------------------------------------------------------------------- +/* + * read the terminal pty and write log + */ +void pkgDPkgPM::DoTerminalPty(int master) +{ + unsigned char term_buf[1024] = {0,0, }; + + ssize_t len=read(master, term_buf, sizeof(term_buf)); + if(len == -1 && errno == EIO) + { + // this happens when the child is about to exit, we + // give it time to actually exit, otherwise we run + // into a race so we sleep for half a second. + struct timespec sleepfor = { 0, 500000000 }; + nanosleep(&sleepfor, NULL); + return; + } + if(len <= 0) + return; + FileFd::Write(1, term_buf, len); + if(d->term_out) + fwrite(term_buf, len, sizeof(char), d->term_out); +} + /*}}}*/ +// DPkgPM::ProcessDpkgStatusBuf /*{{{*/ +void pkgDPkgPM::ProcessDpkgStatusLine(char *line) +{ + bool const Debug = _config->FindB("Debug::pkgDPkgProgressReporting",false); + if (Debug == true) + std::clog << "got from dpkg '" << line << "'" << std::endl; + + /* dpkg sends strings like this: + 'status: <pkg>: <pkg qstate>' + 'status: <pkg>:<arch>: <pkg qstate>' + + 'processing: {install,upgrade,configure,remove,purge,disappear,trigproc}: pkg' + 'processing: {install,upgrade,configure,remove,purge,disappear,trigproc}: trigger' + */ + + // we need to split on ": " (note the appended space) as the ':' is + // part of the pkgname:arch information that dpkg sends + // + // A dpkg error message may contain additional ":" (like + // "failed in buffer_write(fd) (10, ret=-1): backend dpkg-deb ..." + // so we need to ensure to not split too much + std::vector<std::string> list = StringSplit(line, ": ", 4); + if(list.size() < 3) + { + if (Debug == true) + std::clog << "ignoring line: not enough ':'" << std::endl; + return; + } + + // build the (prefix, pkgname, action) tuple, position of this + // is different for "processing" or "status" messages + std::string prefix = APT::String::Strip(list[0]); + std::string pkgname; + std::string action; + + // "processing" has the form "processing: action: pkg or trigger" + // with action = ["install", "upgrade", "configure", "remove", "purge", + // "disappear", "trigproc"] + if (prefix == "processing") + { + pkgname = APT::String::Strip(list[2]); + action = APT::String::Strip(list[1]); + } + // "status" has the form: "status: pkg: state" + // with state in ["half-installed", "unpacked", "half-configured", + // "installed", "config-files", "not-installed"] + else if (prefix == "status") + { + pkgname = APT::String::Strip(list[1]); + action = APT::String::Strip(list[2]); + + /* handle the special cases first: + + errors look like this: + 'status: /var/cache/apt/archives/krecipes_0.8.1-0ubuntu1_i386.deb : error : trying to overwrite `/usr/share/doc/kde/HTML/en/krecipes/krectip.png', which is also in package krecipes-data + and conffile-prompt like this + 'status:/etc/compiz.conf/compiz.conf : conffile-prompt: 'current-conffile' 'new-conffile' useredited distedited + */ + if(action == "error") + { + d->progress->Error(pkgname, PackagesDone, PackagesTotal, list[3]); + ++pkgFailures; + WriteApportReport(pkgname.c_str(), list[3].c_str()); + return; + } + else if(action == "conffile-prompt") + { + d->progress->ConffilePrompt(pkgname, PackagesDone, PackagesTotal, list[3]); + return; + } + } else { + if (Debug == true) + std::clog << "unknown prefix '" << prefix << "'" << std::endl; + return; + } + + // At this point we have a pkgname, but it might not be arch-qualified ! + if (pkgname.find(":") == std::string::npos) + { + pkgCache::GrpIterator const Grp = Cache.FindGrp(pkgname); + if (unlikely(Grp.end()== true)) + { + if (Debug == true) + std::clog << "unable to figure out which package is dpkg referring to with '" << pkgname << "'! (0)" << std::endl; + return; + } + /* No arch means that dpkg believes there can only be one package + this can refer to so lets see what could be candidates here: */ + std::vector<pkgCache::PkgIterator> candset; + for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P)) + { + if (PackageOps.find(P.FullName()) != PackageOps.end()) + candset.push_back(P); + // packages can disappear without them having any interaction itself + // so we have to consider these as candidates, too + else if (P->CurrentVer != 0 && action == "disappear") + candset.push_back(P); + } + if (unlikely(candset.empty())) + { + if (Debug == true) + std::clog << "unable to figure out which package is dpkg referring to with '" << pkgname << "'! (1)" << std::endl; + return; + } + else if (candset.size() == 1) // we are lucky + pkgname = candset.cbegin()->FullName(); + else + { + /* here be dragons^Wassumptions about dpkg: + - an M-A:same version is always arch-qualified + - a package from a foreign arch is (in newer versions) */ + size_t installedInstances = 0, wannabeInstances = 0; + for (auto const &P: candset) + { + if (P->CurrentVer != 0) + { + ++installedInstances; + if (Cache[P].Delete() == false) + ++wannabeInstances; + } + else if (Cache[P].Install()) + ++wannabeInstances; + } + // the package becomes M-A:same, so we are still talking about current + if (installedInstances == 1 && wannabeInstances >= 2) + { + for (auto const &P: candset) + { + if (P->CurrentVer == 0) + continue; + pkgname = P.FullName(); + break; + } + } + // the package was M-A:same, it isn't now, so we can only talk about that + else if (installedInstances >= 2 && wannabeInstances == 1) + { + for (auto const &P: candset) + { + auto const IV = Cache[P].InstVerIter(Cache); + if (IV.end()) + continue; + pkgname = P.FullName(); + break; + } + } + // that is a crossgrade + else if (installedInstances == 1 && wannabeInstances == 1 && candset.size() == 2) + { + auto const PkgHasCurrentVersion = [](pkgCache::PkgIterator const &P) { return P->CurrentVer != 0; }; + auto const P = std::find_if(candset.begin(), candset.end(), PkgHasCurrentVersion); + if (unlikely(P == candset.end())) + { + if (Debug == true) + std::clog << "situation for '" << pkgname << "' looked like a crossgrade, but no current version?!" << std::endl; + return; + } + auto fullname = P->FullName(); + if (PackageOps[fullname].size() != PackageOpsDone[fullname]) + pkgname = std::move(fullname); + else + { + auto const pkgi = std::find_if_not(candset.begin(), candset.end(), PkgHasCurrentVersion); + if (unlikely(pkgi == candset.end())) + { + if (Debug == true) + std::clog << "situation for '" << pkgname << "' looked like a crossgrade, but all are installed?!" << std::endl; + return; + } + pkgname = pkgi->FullName(); + } + } + // we are desperate: so "just" take the native one, but that might change mid-air, + // so we have to ask dpkg what it believes native is at the moment… all the time + else + { + std::vector<std::string> sArgs = debSystem::GetDpkgBaseCommand(); + sArgs.push_back("--print-architecture"); + int outputFd = -1; + pid_t const dpkgNativeArch = debSystem::ExecDpkg(sArgs, nullptr, &outputFd, true); + if (unlikely(dpkgNativeArch == -1)) + { + if (Debug == true) + std::clog << "calling dpkg failed to ask it for its current native architecture to expand '" << pkgname << "'!" << std::endl; + return; + } + FILE *dpkg = fdopen(outputFd, "r"); + if(dpkg != NULL) + { + char* buf = NULL; + size_t bufsize = 0; + if (getline(&buf, &bufsize, dpkg) != -1) + pkgname += ':' + bufsize; + free(buf); + fclose(dpkg); + } + ExecWait(dpkgNativeArch, "dpkg --print-architecture", true); + if (pkgname.find(':') != std::string::npos) + { + if (Debug == true) + std::clog << "unable to figure out which package is dpkg referring to with '" << pkgname << "'! (2)" << std::endl; + return; + } + } + } + } + + std::string arch = ""; + if (pkgname.find(":") != string::npos) + arch = StringSplit(pkgname, ":")[1]; + std::string i18n_pkgname = pkgname; + if (arch.size() != 0) + strprintf(i18n_pkgname, "%s (%s)", StringSplit(pkgname, ":")[0].c_str(), arch.c_str()); + + // 'processing' from dpkg looks like + // 'processing: action: pkg' + if(prefix == "processing") + { + auto const iter = std::find_if(PackageProcessingOpsBegin, PackageProcessingOpsEnd, MatchProcessingOp(action.c_str())); + if(iter == PackageProcessingOpsEnd) + { + if (Debug == true) + std::clog << "ignoring unknown action: " << action << std::endl; + return; + } + std::string msg; + strprintf(msg, _(iter->second), i18n_pkgname.c_str()); + d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg); + + // FIXME: this needs a muliarch testcase + // FIXME2: is "pkgname" here reliable with dpkg only sending us + // short pkgnames? + if (action == "disappear") + handleDisappearAction(pkgname); + else if (action == "upgrade") + handleCrossUpgradeAction(pkgname); + return; + } + + if (prefix == "status") + { + std::vector<struct DpkgState> &states = PackageOps[pkgname]; + if(PackageOpsDone[pkgname] < states.size()) + { + char const * next_action = states[PackageOpsDone[pkgname]].state; + if (next_action) + { + /* + if (action == "half-installed" && strcmp("half-configured", next_action) == 0 && + PackageOpsDone[pkg] + 2 < states.size() && action == states[PackageOpsDone[pkg] + 2].state) + { + if (Debug == true) + std::clog << "(parsed from dpkg) pkg: " << short_pkgname << " action: " << action + << " pending trigger defused by unpack" << std::endl; + // unpacking a package defuses the pending trigger + PackageOpsDone[pkg] += 2; + PackagesDone += 2; + next_action = states[PackageOpsDone[pkg]].state; + } + */ + if (Debug == true) + std::clog << "(parsed from dpkg) pkg: " << pkgname + << " action: " << action << " (expected: '" << next_action << "' " + << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl; + + // check if the package moved to the next dpkg state + if(action == next_action) + { + // only read the translation if there is actually a next action + char const * const translation = _(states[PackageOpsDone[pkgname]].str); + + // we moved from one dpkg state to a new one, report that + ++PackageOpsDone[pkgname]; + ++PackagesDone; + + std::string msg; + strprintf(msg, translation, i18n_pkgname.c_str()); + d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg); + } + } + } + else if (action == "triggers-pending") + { + if (Debug == true) + std::clog << "(parsed from dpkg) pkg: " << pkgname + << " action: " << action << " (prefix 2 to " + << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl; + + states.insert(states.begin(), {"installed", N_("Installed %s")}); + states.insert(states.begin(), {"half-configured", N_("Configuring %s")}); + PackagesTotal += 2; + } + } +} + /*}}}*/ +// DPkgPM::handleDisappearAction /*{{{*/ +void pkgDPkgPM::handleDisappearAction(string const &pkgname) +{ + pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname); + if (unlikely(Pkg.end() == true)) + return; + + // a disappeared package has no further actions + auto const ROps = PackageOps[Pkg.FullName()].size(); + auto && ROpsDone = PackageOpsDone[Pkg.FullName()]; + PackagesDone += ROps - ROpsDone; + ROpsDone = ROps; + + // record the package name for display and stuff later + disappearedPkgs.insert(Pkg.FullName(true)); + + // the disappeared package was auto-installed - nothing to do + if ((Cache[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) + return; + pkgCache::VerIterator PkgVer = Cache[Pkg].InstVerIter(Cache); + if (unlikely(PkgVer.end() == true)) + return; + /* search in the list of dependencies for (Pre)Depends, + check if this dependency has a Replaces on our package + and if so transfer the manual installed flag to it */ + for (pkgCache::DepIterator Dep = PkgVer.DependsList(); Dep.end() != true; ++Dep) + { + if (Dep->Type != pkgCache::Dep::Depends && + Dep->Type != pkgCache::Dep::PreDepends) + continue; + pkgCache::PkgIterator Tar = Dep.TargetPkg(); + if (unlikely(Tar.end() == true)) + continue; + // the package is already marked as manual + if ((Cache[Tar].Flags & pkgCache::Flag::Auto) != pkgCache::Flag::Auto) + continue; + pkgCache::VerIterator TarVer = Cache[Tar].InstVerIter(Cache); + if (TarVer.end() == true) + continue; + for (pkgCache::DepIterator Rep = TarVer.DependsList(); Rep.end() != true; ++Rep) + { + if (Rep->Type != pkgCache::Dep::Replaces) + continue; + if (Pkg != Rep.TargetPkg()) + continue; + // okay, they are strongly connected - transfer manual-bit + if (Debug == true) + std::clog << "transfer manual-bit from disappeared »" << pkgname << "« to »" << Tar.FullName() << "«" << std::endl; + Cache[Tar].Flags &= ~Flag::Auto; + break; + } + } +} + /*}}}*/ +void pkgDPkgPM::handleCrossUpgradeAction(string const &pkgname) /*{{{*/ +{ + // in a crossgrade what looked like a remove first is really an unpack over it + auto const Pkg = Cache.FindPkg(pkgname); + if (likely(Pkg.end() == false) && Cache[Pkg].Delete()) + { + auto const Grp = Pkg.Group(); + if (likely(Grp.end() == false)) + { + for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P)) + if(Cache[P].Install()) + { + auto && Ops = PackageOps[P.FullName()]; + auto const unpackOp = std::find_if(Ops.cbegin(), Ops.cend(), [](DpkgState const &s) { return strcmp(s.state, "unpacked") == 0; }); + if (unpackOp != Ops.cend()) + { + // skip ahead in the crossgraded packages + auto const skipped = std::distance(Ops.cbegin(), unpackOp); + PackagesDone += skipped; + PackageOpsDone[P.FullName()] += skipped; + // finish the crossremoved package + auto const ROps = PackageOps[Pkg.FullName()].size(); + auto && ROpsDone = PackageOpsDone[Pkg.FullName()]; + PackagesDone += ROps - ROpsDone; + ROpsDone = ROps; + break; + } + } + } + } +} + /*}}}*/ +// DPkgPM::DoDpkgStatusFd /*{{{*/ +void pkgDPkgPM::DoDpkgStatusFd(int statusfd) +{ + auto const remainingBuffer = (sizeof(d->dpkgbuf) / sizeof(d->dpkgbuf[0])) - d->dpkgbuf_pos; + if (likely(remainingBuffer > 0) && d->status_fd_reached_end_of_file == false) + { + auto const len = read(statusfd, &d->dpkgbuf[d->dpkgbuf_pos], remainingBuffer); + if (len < 0) + return; + else if (len == 0 && d->dpkgbuf_pos == 0) + { + d->status_fd_reached_end_of_file = true; + return; + } + d->dpkgbuf_pos += (len / sizeof(d->dpkgbuf[0])); + } + + // process line by line from the buffer + char *p = d->dpkgbuf, *q = nullptr; + while((q=(char*)memchr(p, '\n', (d->dpkgbuf + d->dpkgbuf_pos) - p)) != nullptr) + { + *q = '\0'; + ProcessDpkgStatusLine(p); + p = q + 1; // continue with next line + } + + // check if we stripped the buffer clean + if (p > (d->dpkgbuf + d->dpkgbuf_pos)) + { + d->dpkgbuf_pos = 0; + return; + } + + // otherwise move the unprocessed tail to the start and update pos + memmove(d->dpkgbuf, p, (p - d->dpkgbuf)); + d->dpkgbuf_pos = (d->dpkgbuf + d->dpkgbuf_pos) - p; +} + /*}}}*/ +// DPkgPM::WriteHistoryTag /*{{{*/ +void pkgDPkgPM::WriteHistoryTag(string const &tag, string value) +{ + size_t const length = value.length(); + if (length == 0) + return; + // poor mans rstrip(", ") + if (value[length-2] == ',' && value[length-1] == ' ') + value.erase(length - 2, 2); + fprintf(d->history_out, "%s: %s\n", tag.c_str(), value.c_str()); +} /*}}}*/ +// DPkgPM::OpenLog /*{{{*/ +bool pkgDPkgPM::OpenLog() +{ + string const logfile_name = _config->FindFile("Dir::Log::Terminal", "/dev/null"); + string logdir = flNotFile(logfile_name); + if(CreateAPTDirectoryIfNeeded(logdir, logdir) == false) + // FIXME: use a better string after freeze + return _error->Error(_("Directory '%s' missing"), logdir.c_str()); + + // get current time + char timestr[200]; + time_t const t = time(NULL); + struct tm tm_buf; + struct tm const * const tmp = localtime_r(&t, &tm_buf); + strftime(timestr, sizeof(timestr), "%F %T", tmp); + + // open terminal log + if (logfile_name != "/dev/null") + { + d->term_out = fopen(logfile_name.c_str(),"a"); + if (d->term_out == NULL) + return _error->WarningE("OpenLog", _("Could not open file '%s'"), logfile_name.c_str()); + setvbuf(d->term_out, NULL, _IONBF, 0); + SetCloseExec(fileno(d->term_out), true); + if (getuid() == 0) // if we aren't root, we can't chown a file, so don't try it + { + struct passwd *pw = getpwnam("root"); + struct group *gr = getgrnam("adm"); + if (pw != NULL && gr != NULL && chown(logfile_name.c_str(), pw->pw_uid, gr->gr_gid) != 0) + _error->WarningE("OpenLog", "chown to root:adm of file %s failed", logfile_name.c_str()); + } + if (chmod(logfile_name.c_str(), 0640) != 0) + _error->WarningE("OpenLog", "chmod 0640 of file %s failed", logfile_name.c_str()); + fprintf(d->term_out, "\nLog started: %s\n", timestr); + } + + // write your history + string const history_name = _config->FindFile("Dir::Log::History", "/dev/null"); + string logdir2 = flNotFile(logfile_name); + if(logdir != logdir2 && CreateAPTDirectoryIfNeeded(logdir2, logdir2) == false) + return _error->Error(_("Directory '%s' missing"), logdir.c_str()); + if (history_name != "/dev/null") + { + d->history_out = fopen(history_name.c_str(),"a"); + if (d->history_out == NULL) + return _error->WarningE("OpenLog", _("Could not open file '%s'"), history_name.c_str()); + SetCloseExec(fileno(d->history_out), true); + chmod(history_name.c_str(), 0644); + fprintf(d->history_out, "\nStart-Date: %s\n", timestr); + string remove, purge, install, reinstall, upgrade, downgrade; + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + enum { CANDIDATE, CANDIDATE_AUTO, CURRENT_CANDIDATE, CURRENT } infostring; + string *line = NULL; + #define HISTORYINFO(X, Y) { line = &X; infostring = Y; } + if (Cache[I].NewInstall() == true) + HISTORYINFO(install, CANDIDATE_AUTO) + else if (Cache[I].ReInstall() == true) + HISTORYINFO(reinstall, CANDIDATE) + else if (Cache[I].Upgrade() == true) + HISTORYINFO(upgrade, CURRENT_CANDIDATE) + else if (Cache[I].Downgrade() == true) + HISTORYINFO(downgrade, CURRENT_CANDIDATE) + else if (Cache[I].Delete() == true) + HISTORYINFO((Cache[I].Purge() ? purge : remove), CURRENT) + else + continue; + #undef HISTORYINFO + line->append(I.FullName(false)).append(" ("); + switch (infostring) { + case CANDIDATE: line->append(Cache[I].CandVersion); break; + case CANDIDATE_AUTO: + line->append(Cache[I].CandVersion); + if ((Cache[I].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) + line->append(", automatic"); + break; + case CURRENT_CANDIDATE: line->append(Cache[I].CurVersion).append(", ").append(Cache[I].CandVersion); break; + case CURRENT: line->append(Cache[I].CurVersion); break; + } + line->append("), "); + } + if (_config->Exists("Commandline::AsString") == true) + WriteHistoryTag("Commandline", _config->Find("Commandline::AsString")); + std::string RequestingUser = AptHistoryRequestingUser(); + if (RequestingUser != "") + WriteHistoryTag("Requested-By", RequestingUser); + WriteHistoryTag("Install", install); + WriteHistoryTag("Reinstall", reinstall); + WriteHistoryTag("Upgrade", upgrade); + WriteHistoryTag("Downgrade",downgrade); + WriteHistoryTag("Remove",remove); + WriteHistoryTag("Purge",purge); + fflush(d->history_out); + } + + return true; +} + /*}}}*/ +// DPkg::CloseLog /*{{{*/ +bool pkgDPkgPM::CloseLog() +{ + char timestr[200]; + time_t t = time(NULL); + struct tm tm_buf; + struct tm *tmp = localtime_r(&t, &tm_buf); + strftime(timestr, sizeof(timestr), "%F %T", tmp); + + if(d->term_out) + { + fprintf(d->term_out, "Log ended: "); + fprintf(d->term_out, "%s", timestr); + fprintf(d->term_out, "\n"); + fclose(d->term_out); + } + d->term_out = NULL; + + if(d->history_out) + { + if (disappearedPkgs.empty() == false) + { + string disappear; + for (std::set<std::string>::const_iterator d = disappearedPkgs.begin(); + d != disappearedPkgs.end(); ++d) + { + pkgCache::PkgIterator P = Cache.FindPkg(*d); + disappear.append(*d); + if (P.end() == true) + disappear.append(", "); + else + disappear.append(" (").append(Cache[P].CurVersion).append("), "); + } + WriteHistoryTag("Disappeared", disappear); + } + if (d->dpkg_error.empty() == false) + fprintf(d->history_out, "Error: %s\n", d->dpkg_error.c_str()); + fprintf(d->history_out, "End-Date: %s\n", timestr); + fclose(d->history_out); + } + d->history_out = NULL; + + return true; +} + /*}}}*/ + +// DPkgPM::BuildPackagesProgressMap /*{{{*/ +void pkgDPkgPM::BuildPackagesProgressMap() +{ + // map the dpkg states to the operations that are performed + // (this is sorted in the same way as Item::Ops) + static const std::array<std::array<DpkgState, 2>, 4> DpkgStatesOpMap = {{ + // Install operation + {{ + {"half-installed", N_("Unpacking %s")}, + {"unpacked", N_("Installing %s") }, + }}, + // Configure operation + {{ + {"half-configured", N_("Configuring %s") }, + { "installed", N_("Installed %s")}, + }}, + // Remove operation + {{ + {"half-configured", N_("Removing %s")}, + {"half-installed", N_("Removing %s")}, + }}, + // Purge operation + {{ + {"config-files", N_("Completely removing %s")}, + {"not-installed", N_("Completely removed %s")}, + }}, + }}; + static_assert(Item::Purge == 3, "Enum item has unexpected index for mapping array"); + + // init the PackageOps map, go over the list of packages that + // that will be [installed|configured|removed|purged] and add + // them to the PackageOps map (the dpkg states it goes through) + // and the PackageOpsTranslations (human readable strings) + for (auto &&I : List) + { + if(I.Pkg.end() == true) + continue; + + string const name = I.Pkg.FullName(); + PackageOpsDone[name] = 0; + auto AddToPackageOps = [&](decltype(I.Op) const Op) { + auto const DpkgOps = DpkgStatesOpMap[Op]; + std::copy(DpkgOps.begin(), DpkgOps.end(), std::back_inserter(PackageOps[name])); + PackagesTotal += DpkgOps.size(); + }; + // purging a package which is installed first passes through remove states + if (I.Op == Item::Purge && I.Pkg->CurrentVer != 0) + AddToPackageOps(Item::Remove); + AddToPackageOps(I.Op); + + if ((I.Op == Item::Remove || I.Op == Item::Purge) && I.Pkg->CurrentVer != 0) + { + if (I.Pkg->CurrentState == pkgCache::State::UnPacked || + I.Pkg->CurrentState == pkgCache::State::HalfInstalled) + { + if (likely(strcmp(PackageOps[name][0].state, "half-configured") == 0)) + { + ++PackageOpsDone[name]; + --PackagesTotal; + } + } + } + } + /* one extra: We don't want the progress bar to reach 100%, especially not + if we call dpkg --configure --pending and process a bunch of triggers + while showing 100%. Also, spindown takes a while, so never reaching 100% + is way more correct than reaching 100% while still doing stuff even if + doing it this way is slightly bending the rules */ + ++PackagesTotal; +} + /*}}}*/ +bool pkgDPkgPM::Go(int StatusFd) /*{{{*/ +{ + APT::Progress::PackageManager *progress = NULL; + if (StatusFd == -1) + progress = APT::Progress::PackageManagerProgressFactory(); + else + progress = new APT::Progress::PackageManagerProgressFd(StatusFd); + + return Go(progress); +} + /*}}}*/ +void pkgDPkgPM::StartPtyMagic() /*{{{*/ +{ + if (_config->FindB("Dpkg::Use-Pty", true) == false) + { + d->master = -1; + if (d->slave != NULL) + free(d->slave); + d->slave = NULL; + return; + } + + if (isatty(STDIN_FILENO) == 0) + d->direct_stdin = true; + + _error->PushToStack(); + + d->master = posix_openpt(O_RDWR | O_NOCTTY); + if (d->master == -1) + _error->Errno("posix_openpt", _("Can not write log (%s)"), _("Is /dev/pts mounted?")); + else if (unlockpt(d->master) == -1) + _error->Errno("unlockpt", "Unlocking the slave of master fd %d failed!", d->master); + else + { +#ifdef HAVE_PTSNAME_R + char slave_name[64]; // 64 is used by bionic + if (ptsname_r(d->master, slave_name, sizeof(slave_name)) != 0) +#else + char const * const slave_name = ptsname(d->master); + if (slave_name == NULL) +#endif + _error->Errno("ptsname", "Getting name for slave of master fd %d failed!", d->master); + else + { + d->slave = strdup(slave_name); + if (d->slave == NULL) + _error->Errno("strdup", "Copying name %s for slave of master fd %d failed!", slave_name, d->master); + else if (grantpt(d->master) == -1) + _error->Errno("grantpt", "Granting access to slave %s based on master fd %d failed!", slave_name, d->master); + else if (tcgetattr(STDIN_FILENO, &d->tt) == 0) + { + d->tt_is_valid = true; + struct termios raw_tt; + // copy window size of stdout if its a 'good' terminal + if (tcgetattr(STDOUT_FILENO, &raw_tt) == 0) + { + struct winsize win; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) < 0) + _error->Errno("ioctl", "Getting TIOCGWINSZ from stdout failed!"); + if (ioctl(d->master, TIOCSWINSZ, &win) < 0) + _error->Errno("ioctl", "Setting TIOCSWINSZ for master fd %d failed!", d->master); + } + if (tcsetattr(d->master, TCSANOW, &d->tt) == -1) + _error->Errno("tcsetattr", "Setting in Start via TCSANOW for master fd %d failed!", d->master); + + raw_tt = d->tt; + cfmakeraw(&raw_tt); + raw_tt.c_lflag &= ~ECHO; + raw_tt.c_lflag |= ISIG; + // block SIGTTOU during tcsetattr to prevent a hang if + // the process is a member of the background process group + // http://www.opengroup.org/onlinepubs/000095399/functions/tcsetattr.html + sigemptyset(&d->sigmask); + sigaddset(&d->sigmask, SIGTTOU); + sigprocmask(SIG_BLOCK,&d->sigmask, &d->original_sigmask); + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_tt) == -1) + _error->Errno("tcsetattr", "Setting in Start via TCSAFLUSH for stdin failed!"); + sigprocmask(SIG_SETMASK, &d->original_sigmask, NULL); + + } + if (d->slave != NULL) + { + /* on linux, closing (and later reopening) all references to the slave + makes the slave a death end, so we open it here to have one open all + the time. We could use this fd in SetupSlavePtyMagic() for linux, but + on kfreebsd we get an incorrect ("step like") output then while it has + no problem with closing all references… so to avoid platform specific + code here we combine both and be happy once more */ + d->protect_slave_from_dying = open(d->slave, O_RDWR | O_CLOEXEC | O_NOCTTY); + } + } + } + + if (_error->PendingError() == true) + { + if (d->master != -1) + { + close(d->master); + d->master = -1; + } + if (d->slave != NULL) + { + free(d->slave); + d->slave = NULL; + } + _error->DumpErrors(std::cerr, GlobalError::DEBUG, false); + } + _error->RevertToStack(); +} + /*}}}*/ +void pkgDPkgPM::SetupSlavePtyMagic() /*{{{*/ +{ + if(d->master == -1 || d->slave == NULL) + return; + + if (close(d->master) == -1) + _error->FatalE("close", "Closing master %d in child failed!", d->master); + d->master = -1; + if (setsid() == -1) + _error->FatalE("setsid", "Starting a new session for child failed!"); + + int const slaveFd = open(d->slave, O_RDWR | O_NOCTTY); + if (slaveFd == -1) + _error->FatalE("open", _("Can not write log (%s)"), _("Is /dev/pts mounted?")); + else if (ioctl(slaveFd, TIOCSCTTY, 0) < 0) + _error->FatalE("ioctl", "Setting TIOCSCTTY for slave fd %d failed!", slaveFd); + else + { + unsigned short i = 0; + if (d->direct_stdin == true) + ++i; + for (; i < 3; ++i) + if (dup2(slaveFd, i) == -1) + _error->FatalE("dup2", "Dupping %d to %d in child failed!", slaveFd, i); + + if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSANOW, &d->tt) < 0) + _error->FatalE("tcsetattr", "Setting in Setup via TCSANOW for slave fd %d failed!", slaveFd); + } + + if (slaveFd != -1) + close(slaveFd); +} + /*}}}*/ +void pkgDPkgPM::StopPtyMagic() /*{{{*/ +{ + if (d->slave != NULL) + free(d->slave); + d->slave = NULL; + if (d->protect_slave_from_dying != -1) + { + close(d->protect_slave_from_dying); + d->protect_slave_from_dying = -1; + } + if(d->master >= 0) + { + if (d->tt_is_valid == true && tcsetattr(STDIN_FILENO, TCSAFLUSH, &d->tt) == -1) + _error->FatalE("tcsetattr", "Setting in Stop via TCSAFLUSH for stdin failed!"); + close(d->master); + d->master = -1; + } +} + /*}}}*/ +static void cleanUpTmpDir(char * const tmpdir) /*{{{*/ +{ + if (tmpdir == nullptr) + return; + DIR * const D = opendir(tmpdir); + if (D == nullptr) + _error->Errno("opendir", _("Unable to read %s"), tmpdir); + else + { + auto const dfd = dirfd(D); + for (struct dirent *Ent = readdir(D); Ent != nullptr; Ent = readdir(D)) + { + if (Ent->d_name[0] == '.') + continue; +#ifdef _DIRENT_HAVE_D_TYPE + if (unlikely(Ent->d_type != DT_LNK && Ent->d_type != DT_UNKNOWN)) + continue; +#endif + if (unlikely(unlinkat(dfd, Ent->d_name, 0) != 0)) + break; + } + closedir(D); + rmdir(tmpdir); + } + free(tmpdir); +} + /*}}}*/ + +// DPkgPM::Go - Run the sequence /*{{{*/ +// --------------------------------------------------------------------- +/* This globs the operations and calls dpkg + * + * If it is called with a progress object apt will report the install + * progress to this object. It maps the dpkg states a package goes + * through to human readable (and i10n-able) + * names and calculates a percentage for each step. + */ +static bool ItemIsEssential(pkgDPkgPM::Item const &I) +{ + static auto const cachegen = _config->Find("pkgCacheGen::Essential"); + if (cachegen == "none" || cachegen == "native") + return true; + if (unlikely(I.Pkg.end())) + return true; + return (I.Pkg->Flags & pkgCache::Flag::Essential) != 0; +} +bool pkgDPkgPM::ExpandPendingCalls(std::vector<Item> &List, pkgDepCache &Cache) +{ + { + std::unordered_set<decltype(pkgCache::Package::ID)> alreadyRemoved; + for (auto && I : List) + if (I.Op == Item::Remove || I.Op == Item::Purge) + alreadyRemoved.insert(I.Pkg->ID); + std::remove_reference<decltype(List)>::type AppendList; + for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg) + if (Cache[Pkg].Delete() && alreadyRemoved.insert(Pkg->ID).second == true) + AppendList.emplace_back(Cache[Pkg].Purge() ? Item::Purge : Item::Remove, Pkg); + std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List)); + } + { + std::unordered_set<decltype(pkgCache::Package::ID)> alreadyConfigured; + for (auto && I : List) + if (I.Op == Item::Configure) + alreadyConfigured.insert(I.Pkg->ID); + std::remove_reference<decltype(List)>::type AppendList; + for (auto && I : List) + if (I.Op == Item::Install && alreadyConfigured.insert(I.Pkg->ID).second == true) + AppendList.emplace_back(Item::Configure, I.Pkg); + for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg) + if (Pkg.State() == pkgCache::PkgIterator::NeedsConfigure && + Cache[Pkg].Delete() == false && alreadyConfigured.insert(Pkg->ID).second == true) + AppendList.emplace_back(Item::Configure, Pkg); + std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List)); + } + return true; +} +bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) +{ + struct Inhibitor + { + int Fd = -1; + Inhibitor() + { + if (_config->FindB("DPkg::Inhibit-Shutdown", true)) + Fd = Inhibit("shutdown", "APT", "APT is installing or removing packages", "block"); + } + ~Inhibitor() + { + if (Fd > 0) + close(Fd); + } + } inhibitor; + + // explicitly remove&configure everything for hookscripts and progress building + // we need them only temporarily through, so keep the length and erase afterwards + decltype(List)::const_iterator::difference_type explicitIdx = + std::distance(List.cbegin(), List.cend()); + ExpandPendingCalls(List, Cache); + + /* if dpkg told us that it has already done everything to the package we wanted it to do, + we shouldn't ask it for "more" later. That can e.g. happen if packages without conffiles + are purged as they will have pass through the purge states on remove already */ + auto const StripAlreadyDoneFrom = [&](APT::VersionVector & Pending) { + Pending.erase(std::remove_if(Pending.begin(), Pending.end(), [&](pkgCache::VerIterator const &Ver) { + auto const PN = Ver.ParentPkg().FullName(); + auto const POD = PackageOpsDone.find(PN); + if (POD == PackageOpsDone.end()) + return false; + return PackageOps[PN].size() <= POD->second; + }), Pending.end()); + }; + + pkgPackageManager::SigINTStop = false; + d->progress = progress; + + // Generate the base argument list for dpkg + std::vector<std::string> const sArgs = debSystem::GetDpkgBaseCommand(); + std::vector<const char *> Args(sArgs.size(), NULL); + std::transform(sArgs.begin(), sArgs.end(), Args.begin(), + [](std::string const &s) { return s.c_str(); }); + unsigned long long const StartSize = std::accumulate(sArgs.begin(), sArgs.end(), 0llu, + [](unsigned long long const i, std::string const &s) { return i + s.length(); }); + size_t const BaseArgs = Args.size(); + + fd_set rfds; + struct timespec tv; + + // try to figure out the max environment size + int OSArgMax = sysconf(_SC_ARG_MAX); + if(OSArgMax < 0) + OSArgMax = 32*1024; + OSArgMax -= EnvironmentSize() - 2*1024; + unsigned int const MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes", OSArgMax); + bool const NoTriggers = _config->FindB("DPkg::NoTriggers", true); + + if (RunScripts("DPkg::Pre-Invoke") == false) + return false; + + if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false) + return false; + + auto const noopDPkgInvocation = _config->FindB("Debug::pkgDPkgPM",false); + // store auto-bits as they are supposed to be after dpkg is run + if (noopDPkgInvocation == false) + Cache.writeStateFile(NULL); + + bool dpkg_recursive_install = _config->FindB("dpkg::install::recursive", false); + if (_config->FindB("dpkg::install::recursive::force", false) == false) + { + // dpkg uses a sorted treewalk since that version which enables the workaround to work + auto const dpkgpkg = Cache.FindPkg("dpkg"); + if (likely(dpkgpkg.end() == false && dpkgpkg->CurrentVer != 0)) + dpkg_recursive_install = Cache.VS().CmpVersion("1.18.5", dpkgpkg.CurrentVer().VerStr()) <= 0; + } + // no point in doing this dance for a handful of packages only + unsigned int const dpkg_recursive_install_min = _config->FindI("dpkg::install::recursive::minimum", 5); + // FIXME: workaround for dpkg bug, see our ./test-bug-740843-versioned-up-down-breaks test + bool const dpkg_recursive_install_numbered = _config->FindB("dpkg::install::recursive::numbered", true); + + // for the progress + BuildPackagesProgressMap(); + + APT::StateChanges approvedStates; + if (_config->FindB("dpkg::selection::remove::approved", true)) + { + for (auto && I : List) + if (I.Op == Item::Purge) + approvedStates.Purge(FindToBeRemovedVersion(I.Pkg)); + else if (I.Op == Item::Remove) + approvedStates.Remove(FindToBeRemovedVersion(I.Pkg)); + } + + // Skip removes if we install another architecture of this package soon (crossgrade) + // We can't just skip them all the time as it could be an ordering requirement [of another package] + if ((approvedStates.Remove().empty() == false || approvedStates.Purge().empty() == false) && + _config->FindB("dpkg::remove::crossgrade::implicit", true) == true) + { + std::unordered_set<decltype(pkgCache::Package::ID)> crossgraded; + std::vector<std::pair<Item*, std::string>> toCrossgrade; + auto const PlanedEnd = std::next(List.begin(), explicitIdx); + for (auto I = List.begin(); I != PlanedEnd; ++I) + { + if (I->Op != Item::Remove && I->Op != Item::Purge) + continue; + + auto const Grp = I->Pkg.Group(); + size_t installedInstances = 0, wannabeInstances = 0; + bool multiArchInstances = false; + for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg)) + { + if (Pkg->CurrentVer != 0) + { + ++installedInstances; + if (Cache[Pkg].Delete() == false) + ++wannabeInstances; + } + else if (PackageOps.find(Pkg.FullName()) != PackageOps.end()) + ++wannabeInstances; + if (multiArchInstances == false) + { + auto const V = Cache[Pkg].InstVerIter(Cache); + if (V.end() == false && (Pkg->CurrentVer == 0 || V != Pkg.CurrentVer())) + multiArchInstances = ((V->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same); + } + } + /* theoretically the installed check would be enough as some wannabe will + be first and hence be the crossgrade we were looking for, but #844300 + prevents this so we keep these situations explicit removes. + It is also the reason why neither of them can be a M-A:same package */ + if (installedInstances == 1 && wannabeInstances == 1 && multiArchInstances == false) + { + auto const FirstInstall = std::find_if_not(I, List.end(), + [](Item const &i) { return i.Op == Item::Remove || i.Op == Item::Purge; }); + auto const LastInstall = std::find_if_not(FirstInstall, List.end(), + [](Item const &i) { return i.Op == Item::Install; }); + auto const crosser = std::find_if(FirstInstall, LastInstall, + [&I](Item const &i) { return i.Pkg->Group == I->Pkg->Group; }); + if (crosser != LastInstall) + { + crossgraded.insert(I->Pkg->ID); + toCrossgrade.emplace_back(&(*I), crosser->Pkg.FullName()); + } + } + } + for (auto I = PlanedEnd; I != List.end(); ++I) + { + if (I->Op != Item::Remove && I->Op != Item::Purge) + continue; + + auto const Grp = I->Pkg.Group(); + for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg)) + { + if (Pkg == I->Pkg || Cache[Pkg].Install() == false) + continue; + toCrossgrade.emplace_back(&(*I), Pkg.FullName()); + break; + } + } + for (auto C : toCrossgrade) + { + // we never do purges on packages which are crossgraded, even if "requested" + if (C.first->Op == Item::Purge) + { + C.first->Op = Item::Remove; // crossgrades should never be purged + auto && Purges = approvedStates.Purge(); + auto const Ver = std::find_if( +#if __GNUC__ >= 5 || (__GNUC_MINOR__ >= 9 && __GNUC__ >= 4) + Purges.cbegin(), Purges.cend(), +#else + Purges.begin(), Purges.end(), +#endif + [&C](pkgCache::VerIterator const &V) { return V.ParentPkg() == C.first->Pkg; }); + approvedStates.Remove(*Ver); + Purges.erase(Ver); + auto && RemOp = PackageOps[C.first->Pkg.FullName()]; + if (RemOp.size() == 4) + { + RemOp.erase(std::next(RemOp.begin(), 2), RemOp.end()); + PackagesTotal -= 2; + } + else + _error->Warning("Unexpected amount of planned ops for package %s: %lu", C.first->Pkg.FullName().c_str(), RemOp.size()); + } + } + if (crossgraded.empty() == false) + { + auto const oldsize = List.size(); + List.erase(std::remove_if(List.begin(), PlanedEnd, + [&crossgraded](Item const &i){ + return (i.Op == Item::Remove || i.Op == Item::Purge) && + crossgraded.find(i.Pkg->ID) != crossgraded.end(); + }), PlanedEnd); + explicitIdx -= (oldsize - List.size()); + } + } + + APT::StateChanges currentStates; + if (_config->FindB("dpkg::selection::current::saveandrestore", true)) + { + for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg) + if (Pkg->CurrentVer == 0) + continue; + else if (Pkg->SelectedState == pkgCache::State::Purge) + currentStates.Purge(FindToBeRemovedVersion(Pkg)); + else if (Pkg->SelectedState == pkgCache::State::DeInstall) + currentStates.Remove(FindToBeRemovedVersion(Pkg)); + if (currentStates.empty() == false) + { + APT::StateChanges cleanStates; + for (auto && P: currentStates.Remove()) + cleanStates.Install(P); + for (auto && P: currentStates.Purge()) + cleanStates.Install(P); + if (cleanStates.Save(false) == false) + return _error->Error("Couldn't clean the currently selected dpkg states"); + } + } + + if (_config->FindB("dpkg::selection::remove::approved", true)) + { + if (approvedStates.Save(false) == false) + { + _error->Error("Couldn't record the approved state changes as dpkg selection states"); + if (currentStates.Save(false) == false) + _error->Error("Couldn't restore dpkg selection states which were present before this interaction!"); + return false; + } + + List.erase(std::next(List.begin(), explicitIdx), List.end()); + + std::vector<bool> toBeRemoved(Cache.Head().PackageCount, false); + for (auto && I: approvedStates.Remove()) + toBeRemoved[I.ParentPkg()->ID] = true; + for (auto && I: approvedStates.Purge()) + toBeRemoved[I.ParentPkg()->ID] = true; + + for (auto && I: List) + if (I.Op == Item::Remove || I.Op == Item::Purge) + toBeRemoved[I.Pkg->ID] = false; + + bool const RemovePending = std::find(toBeRemoved.begin(), toBeRemoved.end(), true) != toBeRemoved.end(); + bool const PurgePending = approvedStates.Purge().empty() == false; + if (RemovePending != false || PurgePending != false) + List.emplace_back(Item::ConfigurePending, pkgCache::PkgIterator()); + if (RemovePending) + List.emplace_back(Item::RemovePending, pkgCache::PkgIterator()); + if (PurgePending) + List.emplace_back(Item::PurgePending, pkgCache::PkgIterator()); + + // support subpressing of triggers processing for special + // cases like d-i that runs the triggers handling manually + if (_config->FindB("DPkg::ConfigurePending", true)) + List.emplace_back(Item::ConfigurePending, pkgCache::PkgIterator()); + } + bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false); + + d->stdin_is_dev_null = false; + + // create log + OpenLog(); + + bool dpkgMultiArch = debSystem::SupportsMultiArch(); + + // start pty magic before the loop + StartPtyMagic(); + + // Tell the progress that its starting and fork dpkg + d->progress->Start(d->master); + + // this loop is runs once per dpkg operation + vector<Item>::const_iterator I = List.cbegin(); + while (I != List.end()) + { + // Do all actions with the same Op in one run + vector<Item>::const_iterator J = I; + if (TriggersPending == true) + for (; J != List.end(); ++J) + { + if (J->Op == I->Op) + continue; + if (J->Op != Item::TriggersPending) + break; + vector<Item>::const_iterator T = J + 1; + if (T != List.end() && T->Op == I->Op) + continue; + break; + } + else if (J->Op == Item::Remove || J->Op == Item::Purge) + J = std::find_if(J, List.cend(), [](Item const &I) { return I.Op != Item::Remove && I.Op != Item::Purge; }); + else + J = std::find_if(J, List.cend(), [&J](Item const &I) { return I.Op != J->Op; }); + + auto const size = (J - I) + 10; + + // start with the baseset of arguments + auto Size = StartSize; + Args.erase(Args.begin() + BaseArgs, Args.end()); + Args.reserve(size); + // keep track of allocated strings for multiarch package names + std::vector<char *> Packages(size, nullptr); + + int fd[2]; + if (pipe(fd) != 0) + return _error->Errno("pipe","Failed to create IPC pipe to dpkg"); + +#define ADDARG(X) Args.push_back(X); Size += strlen(X) +#define ADDARGC(X) Args.push_back(X); Size += sizeof(X) - 1 + + ADDARGC("--status-fd"); + char status_fd_buf[20]; + snprintf(status_fd_buf,sizeof(status_fd_buf),"%i", fd[1]); + ADDARG(status_fd_buf); + unsigned long const Op = I->Op; + + if (NoTriggers == true && I->Op != Item::TriggersPending && + (I->Op != Item::ConfigurePending || std::next(I) != List.end())) + { + ADDARGC("--no-triggers"); + } + + switch (I->Op) + { + case Item::Remove: + case Item::Purge: + ADDARGC("--force-depends"); + if (std::any_of(I, J, ItemIsEssential)) + { + ADDARGC("--force-remove-essential"); + } + ADDARGC("--remove"); + break; + + case Item::Configure: + ADDARGC("--configure"); + break; + + case Item::ConfigurePending: + ADDARGC("--configure"); + ADDARGC("--pending"); + break; + + case Item::TriggersPending: + ADDARGC("--triggers-only"); + ADDARGC("--pending"); + break; + + case Item::RemovePending: + ADDARGC("--remove"); + ADDARGC("--pending"); + break; + + case Item::PurgePending: + ADDARGC("--purge"); + ADDARGC("--pending"); + break; + + case Item::Install: + ADDARGC("--unpack"); + ADDARGC("--auto-deconfigure"); + break; + } + + char * tmpdir_to_free = nullptr; + + // Write in the file or package names + if (I->Op == Item::Install) + { + auto const installsToDo = J - I; + if (dpkg_recursive_install == true && dpkg_recursive_install_min < installsToDo) + { + std::string tmpdir; + strprintf(tmpdir, "%s/apt-dpkg-install-XXXXXX", GetTempDir().c_str()); + tmpdir_to_free = strndup(tmpdir.data(), tmpdir.length()); + if (mkdtemp(tmpdir_to_free) == nullptr) + return _error->Errno("DPkg::Go", "mkdtemp of %s failed in preparation of calling dpkg unpack", tmpdir_to_free); + + char p = 1; + for (auto c = installsToDo - 1; (c = c/10) != 0; ++p); + for (unsigned long n = 0; I != J; ++n, ++I) + { + if (I->File[0] != '/') + return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str()); + auto file = flNotDir(I->File); + if (flExtension(file) != "deb") + file.append(".deb"); + std::string linkpath; + if (dpkg_recursive_install_numbered) + strprintf(linkpath, "%s/%.*lu-%s", tmpdir_to_free, p, n, file.c_str()); + else + strprintf(linkpath, "%s/%s", tmpdir_to_free, file.c_str()); + if (symlink(I->File.c_str(), linkpath.c_str()) != 0) + return _error->Errno("DPkg::Go", "Symlinking %s to %s failed!", I->File.c_str(), linkpath.c_str()); + } + ADDARGC("--recursive"); + ADDARG(tmpdir_to_free); + } + else + { + for (;I != J && Size < MaxArgBytes; ++I) + { + if (I->File[0] != '/') + return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str()); + Args.push_back(I->File.c_str()); + Size += I->File.length(); + } + } + } + else if (I->Op == Item::RemovePending) + { + ++I; + StripAlreadyDoneFrom(approvedStates.Remove()); + if (approvedStates.Remove().empty()) + continue; + } + else if (I->Op == Item::PurgePending) + { + ++I; + // explicit removes of packages without conffiles passthrough the purge states instantly, too. + // Setting these non-installed packages up for purging generates 'unknown pkg' warnings from dpkg + StripAlreadyDoneFrom(approvedStates.Purge()); + if (approvedStates.Purge().empty()) + continue; + std::remove_reference<decltype(approvedStates.Remove())>::type approvedRemoves; + std::swap(approvedRemoves, approvedStates.Remove()); + // we apply it again here as an explicit remove in the ordering will have cleared the purge state + if (approvedStates.Save(false) == false) + { + _error->Error("Couldn't record the approved purges as dpkg selection states"); + if (currentStates.Save(false) == false) + _error->Error("Couldn't restore dpkg selection states which were present before this interaction!"); + return false; + } + std::swap(approvedRemoves, approvedStates.Remove()); + } + else + { + string const nativeArch = _config->Find("APT::Architecture"); + unsigned long const oldSize = I->Pkg.end() == false ? Size : 0; + for (;I != J && Size < MaxArgBytes; ++I) + { + if((*I).Pkg.end() == true) + continue; + if (I->Op == Item::Configure && disappearedPkgs.find(I->Pkg.FullName(true)) != disappearedPkgs.end()) + continue; + // We keep this here to allow "smooth" transitions from e.g. multiarch dpkg/ubuntu to dpkg/debian + if (dpkgMultiArch == false && (I->Pkg.Arch() == nativeArch || + strcmp(I->Pkg.Arch(), "all") == 0 || + strcmp(I->Pkg.Arch(), "none") == 0)) + { + char const * const name = I->Pkg.Name(); + ADDARG(name); + } + else + { + pkgCache::VerIterator PkgVer; + std::string name = I->Pkg.Name(); + if (Op == Item::Remove) + PkgVer = I->Pkg.CurrentVer(); + else if (Op == Item::Purge) + { + // we purge later with --purge --pending, so if it isn't installed (aka rc-only), skip it here + PkgVer = I->Pkg.CurrentVer(); + if (PkgVer.end() == true) + continue; + } + else + PkgVer = Cache[I->Pkg].InstVerIter(Cache); + if (strcmp(I->Pkg.Arch(), "none") == 0) + ; // never arch-qualify a package without an arch + else if (PkgVer.end() == false) + name.append(":").append(PkgVer.Arch()); + else + _error->Warning("Can not find PkgVer for '%s'", name.c_str()); + char * const fullname = strdup(name.c_str()); + Packages.push_back(fullname); + ADDARG(fullname); + } + } + // skip configure action if all scheduled packages disappeared + if (oldSize == Size) + continue; + } +#undef ADDARGC +#undef ADDARG + + J = I; + + if (noopDPkgInvocation == true) + { + for (std::vector<const char *>::const_iterator a = Args.begin(); + a != Args.end(); ++a) + clog << *a << ' '; + clog << endl; + for (std::vector<char *>::const_iterator p = Packages.begin(); + p != Packages.end(); ++p) + free(*p); + Packages.clear(); + close(fd[0]); + close(fd[1]); + cleanUpTmpDir(tmpdir_to_free); + continue; + } + Args.push_back(NULL); + + cout << flush; + clog << flush; + cerr << flush; + + /* Mask off sig int/quit. We do this because dpkg also does when + it forks scripts. What happens is that when you hit ctrl-c it sends + it to all processes in the group. Since dpkg ignores the signal + it doesn't die but we do! So we must also ignore it */ + sighandler_t old_SIGQUIT = signal(SIGQUIT,SIG_IGN); + sighandler_t old_SIGINT = signal(SIGINT,SigINT); + + // Check here for any SIGINT + if (pkgPackageManager::SigINTStop && (Op == Item::Remove || Op == Item::Purge || Op == Item::Install)) + break; + + // ignore SIGHUP as well (debian #463030) + sighandler_t old_SIGHUP = signal(SIGHUP,SIG_IGN); + + // now run dpkg + d->progress->StartDpkg(); + std::set<int> KeepFDs; + KeepFDs.insert(fd[1]); + MergeKeepFdsFromConfiguration(KeepFDs); + pid_t Child = ExecFork(KeepFDs); + if (Child == 0) + { + // This is the child + SetupSlavePtyMagic(); + close(fd[0]); // close the read end of the pipe + + debSystem::DpkgChrootDirectory(); + + if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0) + _exit(100); + + if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO)) + { + int Flags; + int dummy = 0; + if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0) + _exit(100); + + // Discard everything in stdin before forking dpkg + if (fcntl(STDIN_FILENO,F_SETFL,Flags | O_NONBLOCK) < 0) + _exit(100); + + while (read(STDIN_FILENO,&dummy,1) == 1); + + if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0) + _exit(100); + } + + // if color support isn't enabled/disabled explicitly tell + // dpkg to use the same state apt is using for its color support + if (_config->FindB("APT::Color", false) == true) + setenv("DPKG_COLORS", "always", 0); + else + setenv("DPKG_COLORS", "never", 0); + + if (dynamic_cast<debSystem*>(_system) != nullptr + && dynamic_cast<debSystem*>(_system)->IsLocked() == true) { + setenv("DPKG_FRONTEND_LOCKED", "true", 1); + } + if (_config->Find("DPkg::Path", "").empty() == false) + setenv("PATH", _config->Find("DPkg::Path", "").c_str(), 1); + + execvp(Args[0], (char**) &Args[0]); + cerr << "Could not exec dpkg!" << endl; + _exit(100); + } + + // we read from dpkg here + int const _dpkgin = fd[0]; + close(fd[1]); // close the write end of the pipe + d->status_fd_reached_end_of_file = false; + + // apply ionice + if (_config->FindB("DPkg::UseIoNice", false) == true) + ionice(Child); + + // setups fds + sigemptyset(&d->sigmask); + sigprocmask(SIG_BLOCK,&d->sigmask,&d->original_sigmask); + + /* free vectors (and therefore memory) as we don't need the included data anymore */ + for (std::vector<char *>::const_iterator p = Packages.begin(); + p != Packages.end(); ++p) + free(*p); + Packages.clear(); + + // the result of the waitpid call + int Status = 0; + int res; + bool waitpid_failure = false; + bool dpkg_finished = false; + do + { + if (dpkg_finished == false) + { + if ((res = waitpid(Child, &Status, WNOHANG)) == Child) + dpkg_finished = true; + else if (res < 0) + { + // error handling, waitpid returned -1 + if (errno == EINTR) + continue; + waitpid_failure = true; + break; + } + } + if (dpkg_finished && d->status_fd_reached_end_of_file) + break; + + // wait for input or output here + FD_ZERO(&rfds); + if (d->master >= 0 && d->direct_stdin == false && d->stdin_is_dev_null == false) + FD_SET(STDIN_FILENO, &rfds); + FD_SET(_dpkgin, &rfds); + if(d->master >= 0) + FD_SET(d->master, &rfds); + tv.tv_sec = 0; + tv.tv_nsec = d->progress->GetPulseInterval(); + auto const select_ret = pselect(max(d->master, _dpkgin)+1, &rfds, NULL, NULL, + &tv, &d->original_sigmask); + d->progress->Pulse(); + if (select_ret == 0) + continue; + else if (select_ret < 0 && errno == EINTR) + continue; + else if (select_ret < 0) + { + perror("select() returned error"); + continue; + } + + if(d->master >= 0 && FD_ISSET(d->master, &rfds)) + DoTerminalPty(d->master); + if(d->master >= 0 && FD_ISSET(0, &rfds)) + DoStdin(d->master); + if(FD_ISSET(_dpkgin, &rfds)) + DoDpkgStatusFd(_dpkgin); + + } while (true); + close(_dpkgin); + + // Restore sig int/quit + signal(SIGQUIT,old_SIGQUIT); + signal(SIGINT,old_SIGINT); + signal(SIGHUP,old_SIGHUP); + + cleanUpTmpDir(tmpdir_to_free); + + if (waitpid_failure == true) + { + strprintf(d->dpkg_error, "Sub-process %s couldn't be waited for.",Args[0]); + _error->Error("%s", d->dpkg_error.c_str()); + break; + } + + // Check for an error code. + if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0) + { + // if it was set to "keep-dpkg-running" then we won't return + // here but keep the loop going and just report it as a error + // for later + bool const stopOnError = _config->FindB("Dpkg::StopOnError",true); + + if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV) + strprintf(d->dpkg_error, "Sub-process %s received a segmentation fault.",Args[0]); + else if (WIFEXITED(Status) != 0) + strprintf(d->dpkg_error, "Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status)); + else + strprintf(d->dpkg_error, "Sub-process %s exited unexpectedly",Args[0]); + _error->Error("%s", d->dpkg_error.c_str()); + + if(stopOnError) + break; + } + } + // dpkg is done at this point + StopPtyMagic(); + CloseLog(); + + if (d->dpkg_error.empty() == false) + { + // no point in resetting packages we already completed removal for + StripAlreadyDoneFrom(approvedStates.Remove()); + StripAlreadyDoneFrom(approvedStates.Purge()); + APT::StateChanges undo; + auto && undoRem = approvedStates.Remove(); + std::move(undoRem.begin(), undoRem.end(), std::back_inserter(undo.Install())); + auto && undoPur = approvedStates.Purge(); + std::move(undoPur.begin(), undoPur.end(), std::back_inserter(undo.Install())); + approvedStates.clear(); + if (undo.Save(false) == false) + _error->Error("Couldn't revert dpkg selection for approved remove/purge after an error was encountered!"); + } + + StripAlreadyDoneFrom(currentStates.Remove()); + StripAlreadyDoneFrom(currentStates.Purge()); + if (currentStates.Save(false) == false) + _error->Error("Couldn't restore dpkg selection states which were present before this interaction!"); + + if (pkgPackageManager::SigINTStop) + _error->Warning(_("Operation was interrupted before it could finish")); + + if (noopDPkgInvocation == false) + { + if (d->dpkg_error.empty() && (PackagesDone + 1) != PackagesTotal) + { + std::string pkglist; + for (auto const &PO: PackageOps) + if (PO.second.size() != PackageOpsDone[PO.first]) + { + if (pkglist.empty() == false) + pkglist.append(" "); + pkglist.append(PO.first); + } + /* who cares about correct progress? As we depend on it for skipping actions + our parsing should be correct. People will no doubt be confused if they see + this message, but the dpkg warning about unknown packages isn't much better + from a user POV and combined we might have a chance to figure out what is wrong */ + _error->Warning("APT had planned for dpkg to do more than it reported back (%u vs %u).\n" + "Affected packages: %s", PackagesDone, PackagesTotal, pkglist.c_str()); + } + + std::string const oldpkgcache = _config->FindFile("Dir::cache::pkgcache"); + if (oldpkgcache.empty() == false && RealFileExists(oldpkgcache) == true && + RemoveFile("pkgDPkgPM::Go", oldpkgcache)) + { + std::string const srcpkgcache = _config->FindFile("Dir::cache::srcpkgcache"); + if (srcpkgcache.empty() == false && RealFileExists(srcpkgcache) == true) + { + _error->PushToStack(); + pkgCacheFile CacheFile; + CacheFile.BuildCaches(NULL, true); + _error->RevertToStack(); + } + } + } + + // disappearing packages can forward their auto-bit + if (disappearedPkgs.empty() == false) + Cache.writeStateFile(NULL); + + d->progress->Stop(); + + if (RunScripts("DPkg::Post-Invoke") == false) + return false; + + return d->dpkg_error.empty(); +} + +void SigINT(int /*sig*/) { + pkgPackageManager::SigINTStop = true; +} + /*}}}*/ +// pkgDpkgPM::Reset - Dump the contents of the command list /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgDPkgPM::Reset() +{ + List.erase(List.begin(),List.end()); +} + /*}}}*/ +// pkgDpkgPM::WriteApportReport - write out error report pkg failure /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) +{ + // If apport doesn't exist or isn't installed do nothing + // This e.g. prevents messages in 'universes' without apport + pkgCache::PkgIterator apportPkg = Cache.FindPkg("apport"); + if (apportPkg.end() == true || apportPkg->CurrentVer == 0) + return; + + string pkgname, reportfile, pkgver, arch; + string::size_type pos; + FILE *report; + + if (_config->FindB("Dpkg::ApportFailureReport", true) == false) + { + std::clog << "configured to not write apport reports" << std::endl; + return; + } + + // only report the first errors + if(pkgFailures > _config->FindI("APT::Apport::MaxReports", 3)) + { + std::clog << _("No apport report written because MaxReports is reached already") << std::endl; + return; + } + + // check if its not a follow up error + const char *needle = dgettext("dpkg", "dependency problems - leaving unconfigured"); + if(strstr(errormsg, needle) != NULL) { + std::clog << _("No apport report written because the error message indicates its a followup error from a previous failure.") << std::endl; + return; + } + + // do not report disk-full failures + if(strstr(errormsg, strerror(ENOSPC)) != NULL) { + std::clog << _("No apport report written because the error message indicates a disk full error") << std::endl; + return; + } + + // do not report out-of-memory failures + if(strstr(errormsg, strerror(ENOMEM)) != NULL || + strstr(errormsg, "failed to allocate memory") != NULL) { + std::clog << _("No apport report written because the error message indicates a out of memory error") << std::endl; + return; + } + + // do not report bugs regarding inaccessible local files + if(strstr(errormsg, strerror(ENOENT)) != NULL || + strstr(errormsg, "cannot access archive") != NULL) { + std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl; + return; + } + + // do not report errors encountered when decompressing packages + if(strstr(errormsg, "--fsys-tarfile returned error exit status 2") != NULL) { + std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl; + return; + } + + // do not report dpkg I/O errors, this is a format string, so we compare + // the prefix and the suffix of the error with the dpkg error message + vector<string> io_errors; + io_errors.push_back(string("failed to read")); + io_errors.push_back(string("failed to write")); + io_errors.push_back(string("failed to seek")); + io_errors.push_back(string("unexpected end of file or stream")); + + for (vector<string>::iterator I = io_errors.begin(); I != io_errors.end(); ++I) + { + vector<string> list = VectorizeString(dgettext("dpkg", (*I).c_str()), '%'); + if (list.size() > 1) { + // we need to split %s, VectorizeString only allows char so we need + // to kill the "s" manually + if (list[1].size() > 1) { + list[1].erase(0, 1); + if(strstr(errormsg, list[0].c_str()) && + strstr(errormsg, list[1].c_str())) { + std::clog << _("No apport report written because the error message indicates a dpkg I/O error") << std::endl; + return; + } + } + } + } + + // get the pkgname and reportfile + pkgname = flNotDir(pkgpath); + pos = pkgname.find('_'); + if(pos != string::npos) + pkgname = pkgname.substr(0, pos); + + // find the package version and source package name + pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname); + if (Pkg.end() == true) + { + if (pos == std::string::npos || _config->FindB("dpkg::install::recursive::numbered", true) == false) + return; + auto const dash = pkgname.find_first_not_of("0123456789"); + if (dash == std::string::npos || pkgname[dash] != '-') + return; + pkgname.erase(0, dash + 1); + Pkg = Cache.FindPkg(pkgname); + if (Pkg.end() == true) + return; + } + pkgCache::VerIterator Ver = Cache.GetCandidateVersion(Pkg); + if (Ver.end() == true) + return; + pkgver = Ver.VerStr() == NULL ? "unknown" : Ver.VerStr(); + + // if the file exists already, we check: + // - if it was reported already (touched by apport). + // If not, we do nothing, otherwise + // we overwrite it. This is the same behaviour as apport + // - if we have a report with the same pkgversion already + // then we skip it + _config->CndSet("Dir::Apport", "var/crash"); + reportfile = flCombine(_config->FindDir("Dir::Apport", "var/crash"), pkgname+".0.crash"); + if(FileExists(reportfile)) + { + struct stat buf; + char strbuf[255]; + + // check atime/mtime + stat(reportfile.c_str(), &buf); + if(buf.st_mtime > buf.st_atime) + return; + + // check if the existing report is the same version + report = fopen(reportfile.c_str(),"r"); + while(fgets(strbuf, sizeof(strbuf), report) != NULL) + { + if(strstr(strbuf,"Package:") == strbuf) + { + char pkgname[255], version[255]; + if(sscanf(strbuf, "Package: %254s %254s", pkgname, version) == 2) + if(strcmp(pkgver.c_str(), version) == 0) + { + fclose(report); + return; + } + } + } + fclose(report); + } + + // now write the report + arch = _config->Find("APT::Architecture"); + report = fopen(reportfile.c_str(),"w"); + if(report == NULL) + return; + if(_config->FindB("DPkgPM::InitialReportOnly",false) == true) + chmod(reportfile.c_str(), 0); + else + chmod(reportfile.c_str(), 0600); + fprintf(report, "ProblemType: Package\n"); + fprintf(report, "Architecture: %s\n", arch.c_str()); + time_t now = time(NULL); + char ctime_buf[26]; // need at least 26 bytes according to ctime(3) + fprintf(report, "Date: %s" , ctime_r(&now, ctime_buf)); + fprintf(report, "Package: %s %s\n", pkgname.c_str(), pkgver.c_str()); + fprintf(report, "SourcePackage: %s\n", Ver.SourcePkgName()); + fprintf(report, "ErrorMessage:\n %s\n", errormsg); + + // ensure that the log is flushed + if(d->term_out) + fflush(d->term_out); + + // attach terminal log it if we have it + string logfile_name = _config->FindFile("Dir::Log::Terminal", "/dev/null"); + if (logfile_name != "/dev/null") + { + FILE *log = NULL; + + fprintf(report, "DpkgTerminalLog:\n"); + log = fopen(logfile_name.c_str(),"r"); + if(log != NULL) + { + char buf[1024]; + while( fgets(buf, sizeof(buf), log) != NULL) + fprintf(report, " %s", buf); + fprintf(report, " \n"); + fclose(log); + } + } + + // attach history log it if we have it + string histfile_name = _config->FindFile("Dir::Log::History", "/dev/null"); + if (histfile_name != "/dev/null") + { + fprintf(report, "DpkgHistoryLog:\n"); + FILE* log = fopen(histfile_name.c_str(),"r"); + if(log != NULL) + { + char buf[1024]; + while( fgets(buf, sizeof(buf), log) != NULL) + fprintf(report, " %s", buf); + fclose(log); + } + } + + // log the ordering, see dpkgpm.h and the "Ops" enum there + fprintf(report, "AptOrdering:\n"); + for (auto && I : List) + { + char const * opstr = nullptr; + switch (I.Op) + { + case Item::Install: opstr = "Install"; break; + case Item::Configure: opstr = "Configure"; break; + case Item::Remove: opstr = "Remove"; break; + case Item::Purge: opstr = "Purge"; break; + case Item::ConfigurePending: opstr = "ConfigurePending"; break; + case Item::TriggersPending: opstr = "TriggersPending"; break; + case Item::RemovePending: opstr = "RemovePending"; break; + case Item::PurgePending: opstr = "PurgePending"; break; + } + auto const pkgname = I.Pkg.end() ? "NULL" : I.Pkg.FullName(); + fprintf(report, " %s: %s\n", pkgname.c_str(), opstr); + } + + // attach dmesg log (to learn about segfaults) + if (FileExists("/bin/dmesg")) + { + fprintf(report, "Dmesg:\n"); + FILE *log = popen("/bin/dmesg","r"); + if(log != NULL) + { + char buf[1024]; + while( fgets(buf, sizeof(buf), log) != NULL) + fprintf(report, " %s", buf); + pclose(log); + } + } + + // attach df -l log (to learn about filesystem status) + if (FileExists("/bin/df")) + { + + fprintf(report, "Df:\n"); + FILE *log = popen("/bin/df -l","r"); + if(log != NULL) + { + char buf[1024]; + while( fgets(buf, sizeof(buf), log) != NULL) + fprintf(report, " %s", buf); + pclose(log); + } + } + + fclose(report); + +} + /*}}}*/ diff --git a/apt-pkg/deb/dpkgpm.h b/apt-pkg/deb/dpkgpm.h new file mode 100644 index 0000000..c073b50 --- /dev/null +++ b/apt-pkg/deb/dpkgpm.h @@ -0,0 +1,144 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + DPKG Package Manager - Provide an interface to dpkg + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_DPKGPM_H +#define PKGLIB_DPKGPM_H + +#include <apt-pkg/macros.h> +#include <apt-pkg/packagemanager.h> +#include <apt-pkg/pkgcache.h> + +#include <map> +#include <string> +#include <vector> +#include <stdio.h> + +#ifndef APT_10_CLEANER_HEADERS +#include <apt-pkg/init.h> +#endif + +class pkgDepCache; +namespace APT { namespace Progress { class PackageManager; } } + +#ifndef APT_8_CLEANER_HEADERS +using std::vector; +using std::map; +#endif + +class pkgDPkgPMPrivate; + + +class pkgDPkgPM : public pkgPackageManager +{ + private: + pkgDPkgPMPrivate * const d; + + /** \brief record the disappear action and handle accordingly + + dpkg let packages disappear then they have no files any longer and + nothing depends on them. We need to collect this as dpkg as well as + APT doesn't know beforehand that the package will disappear, so the + only possible option is to tell the user afterwards about it. + To enhance the experience we also try to forward the auto-install + flag so the disappear-causer(s) are not autoremoved next time - + for the transfer to happen the disappeared version needs to depend + on the package the flag should be forwarded to and this package + needs to declare a Replaces on the disappeared package. + \param pkgname Name of the package that disappeared + */ + APT_HIDDEN void handleDisappearAction(std::string const &pkgname); + APT_HIDDEN void handleCrossUpgradeAction(std::string const &pkgname); + + protected: + int pkgFailures; + + // progress reporting + struct DpkgState + { + const char *state; // the dpkg state (e.g. "unpack") + const char *str; // the human readable translation of the state + }; + + // the dpkg states that the pkg will run through, the string is + // the package, the vector contains the dpkg states that the package + // will go through + std::map<std::string,std::vector<struct DpkgState> > PackageOps; + // the dpkg states that are already done; the string is the package + // the int is the state that is already done (e.g. a package that is + // going to be install is already in state "half-installed") + std::map<std::string,unsigned int> PackageOpsDone; + + // progress reporting + unsigned int PackagesDone; + unsigned int PackagesTotal; + + public: + struct Item + { + enum Ops {Install, Configure, Remove, Purge, ConfigurePending, TriggersPending, + RemovePending, PurgePending } Op; + std::string File; + PkgIterator Pkg; + Item(Ops Op,PkgIterator Pkg,std::string File = "") : Op(Op), + File(File), Pkg(Pkg) {}; + Item() {}; + }; + protected: + std::vector<Item> List; + + // Helpers + bool RunScriptsWithPkgs(const char *Cnf); + APT_DEPRECATED_MSG("Use SendPkgInfo with the version as parameter instead") bool SendV2Pkgs(FILE *F); + bool SendPkgsInfo(FILE * const F, unsigned int const &Version); + void WriteHistoryTag(std::string const &tag, std::string value); + std::string ExpandShortPackageName(pkgDepCache &Cache, + const std::string &short_pkgname); + + // Terminal progress + void SendTerminalProgress(float percentage); + + // apport integration + void WriteApportReport(const char *pkgpath, const char *errormsg); + + // dpkg log + bool OpenLog(); + bool CloseLog(); + + // helper + void BuildPackagesProgressMap(); + void StartPtyMagic(); + void SetupSlavePtyMagic(); + void StopPtyMagic(); + + // input processing + void DoStdin(int master); + void DoTerminalPty(int master); + void DoDpkgStatusFd(int statusfd); + void ProcessDpkgStatusLine(char *line); + + // The Actual installation implementation + virtual bool Install(PkgIterator Pkg,std::string File) APT_OVERRIDE; + virtual bool Configure(PkgIterator Pkg) APT_OVERRIDE; + virtual bool Remove(PkgIterator Pkg,bool Purge = false) APT_OVERRIDE; + + virtual bool Go(APT::Progress::PackageManager *progress) APT_OVERRIDE; + APT_DEPRECATED_MSG("Use overload with explicit progress manager") virtual bool Go(int StatusFd=-1) APT_OVERRIDE; + + virtual void Reset() APT_OVERRIDE; + + public: + + pkgDPkgPM(pkgDepCache *Cache); + virtual ~pkgDPkgPM(); + + APT_HIDDEN static bool ExpandPendingCalls(std::vector<Item> &List, pkgDepCache &Cache); +}; + +void SigINT(int sig); + +#endif diff --git a/apt-pkg/depcache.cc b/apt-pkg/depcache.cc new file mode 100644 index 0000000..510f2d3 --- /dev/null +++ b/apt-pkg/depcache.cc @@ -0,0 +1,2040 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Dependency Cache - Caches Dependency information. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/cachefile.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/prettyprinters.h> +#include <apt-pkg/progress.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile.h> +#include <apt-pkg/version.h> +#include <apt-pkg/versionmatch.h> + +#include <algorithm> +#include <iostream> +#include <list> +#include <set> +#include <string> +#include <utility> +#include <vector> +#include <stdio.h> +#include <string.h> + +#include <sys/stat.h> + +#include <apti18n.h> + /*}}}*/ + +using std::string; + +// helper for Install-Recommends-Sections and Never-MarkAuto-Sections /*{{{*/ +static bool +ConfigValueInSubTree(const char* SubTree, const char *needle) +{ + Configuration::Item const *Opts; + Opts = _config->Tree(SubTree); + if (Opts != 0 && Opts->Child != 0) + { + Opts = Opts->Child; + for (; Opts != 0; Opts = Opts->Next) + { + if (Opts->Value.empty() == true) + continue; + if (strcmp(needle, Opts->Value.c_str()) == 0) + return true; + } + } + return false; +} + /*}}}*/ +pkgDepCache::ActionGroup::ActionGroup(pkgDepCache &cache) : /*{{{*/ + d(NULL), cache(cache), released(false) +{ + ++cache.group_level; +} + +void pkgDepCache::ActionGroup::release() +{ + if(!released) + { + if(cache.group_level == 0) + std::cerr << "W: Unbalanced action groups, expect badness" << std::endl; + else + { + --cache.group_level; + + if(cache.group_level == 0) + cache.MarkAndSweep(); + } + + released = true; + } +} + +pkgDepCache::ActionGroup::~ActionGroup() +{ + release(); +} + /*}}}*/ +// DepCache::pkgDepCache - Constructors /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgDepCache::pkgDepCache(pkgCache * const pCache,Policy * const Plcy) : + group_level(0), Cache(pCache), PkgState(0), DepState(0), + iUsrSize(0), iDownloadSize(0), iInstCount(0), iDelCount(0), iKeepCount(0), + iBrokenCount(0), iPolicyBrokenCount(0), iBadCount(0), d(NULL) +{ + DebugMarker = _config->FindB("Debug::pkgDepCache::Marker", false); + DebugAutoInstall = _config->FindB("Debug::pkgDepCache::AutoInstall", false); + delLocalPolicy = 0; + LocalPolicy = Plcy; + if (LocalPolicy == 0) + delLocalPolicy = LocalPolicy = new Policy; +} + /*}}}*/ +// DepCache::~pkgDepCache - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgDepCache::~pkgDepCache() +{ + delete [] PkgState; + delete [] DepState; + delete delLocalPolicy; +} + /*}}}*/ +// DepCache::Init - Generate the initial extra structures. /*{{{*/ +// --------------------------------------------------------------------- +/* This allocats the extension buffers and initializes them. */ +bool pkgDepCache::Init(OpProgress * const Prog) +{ + // Suppress mark updates during this operation (just in case) and + // run a mark operation when Init terminates. + ActionGroup actions(*this); + + delete [] PkgState; + delete [] DepState; + PkgState = new StateCache[Head().PackageCount]; + DepState = new unsigned char[Head().DependsCount]; + memset(PkgState,0,sizeof(*PkgState)*Head().PackageCount); + memset(DepState,0,sizeof(*DepState)*Head().DependsCount); + + if (Prog != 0) + { + Prog->OverallProgress(0,2*Head().PackageCount,Head().PackageCount, + _("Building dependency tree")); + Prog->SubProgress(Head().PackageCount,_("Candidate versions")); + } + + /* Set the current state of everything. In this state all of the + packages are kept exactly as is. See AllUpgrade */ + int Done = 0; + for (PkgIterator I = PkgBegin(); I.end() != true; ++I, ++Done) + { + if (Prog != 0 && Done%20 == 0) + Prog->Progress(Done); + + // Find the proper cache slot + StateCache &State = PkgState[I->ID]; + State.iFlags = 0; + + // Figure out the install version + State.CandidateVer = LocalPolicy->GetCandidateVer(I); + State.InstallVer = I.CurrentVer(); + State.Mode = ModeKeep; + + State.Update(I,*this); + } + + if (Prog != 0) + { + Prog->OverallProgress(Head().PackageCount,2*Head().PackageCount, + Head().PackageCount, + _("Building dependency tree")); + Prog->SubProgress(Head().PackageCount,_("Dependency generation")); + } + + Update(Prog); + + if(Prog != 0) + Prog->Done(); + + return true; +} + /*}}}*/ +bool pkgDepCache::readStateFile(OpProgress * const Prog) /*{{{*/ +{ + FileFd state_file; + string const state = _config->FindFile("Dir::State::extended_states"); + if(RealFileExists(state)) { + state_file.Open(state, FileFd::ReadOnly, FileFd::Extension); + off_t const file_size = state_file.Size(); + if(Prog != NULL) + Prog->OverallProgress(0, file_size, 1, + _("Reading state information")); + + pkgTagFile tagfile(&state_file); + pkgTagSection section; + off_t amt = 0; + bool const debug_autoremove = _config->FindB("Debug::pkgAutoRemove",false); + while(tagfile.Step(section)) { + string const pkgname = section.FindS("Package"); + string pkgarch = section.FindS("Architecture"); + if (pkgarch.empty() == true) + pkgarch = "any"; + pkgCache::PkgIterator pkg = Cache->FindPkg(pkgname, pkgarch); + // Silently ignore unknown packages and packages with no actual version. + if(pkg.end() == true || pkg->VersionList == 0) + continue; + + short const reason = section.FindI("Auto-Installed", 0); + if(reason > 0) + { + PkgState[pkg->ID].Flags |= Flag::Auto; + if (unlikely(debug_autoremove)) + std::clog << "Auto-Installed : " << pkg.FullName() << std::endl; + if (pkgarch == "any") + { + pkgCache::GrpIterator G = pkg.Group(); + for (pkg = G.NextPkg(pkg); pkg.end() != true; pkg = G.NextPkg(pkg)) + if (pkg->VersionList != 0) + PkgState[pkg->ID].Flags |= Flag::Auto; + } + } + amt += section.size(); + if(Prog != NULL) + Prog->OverallProgress(amt, file_size, 1, + _("Reading state information")); + } + if(Prog != NULL) + Prog->OverallProgress(file_size, file_size, 1, + _("Reading state information")); + } + + return true; +} + /*}}}*/ +bool pkgDepCache::writeStateFile(OpProgress * const /*prog*/, bool const InstalledOnly) /*{{{*/ +{ + bool const debug_autoremove = _config->FindB("Debug::pkgAutoRemove",false); + + if(debug_autoremove) + std::clog << "pkgDepCache::writeStateFile()" << std::endl; + + FileFd StateFile; + string const state = _config->FindFile("Dir::State::extended_states"); + if (CreateAPTDirectoryIfNeeded(_config->FindDir("Dir::State"), flNotFile(state)) == false) + return false; + + // if it does not exist, create a empty one + if(!RealFileExists(state)) + { + StateFile.Open(state, FileFd::WriteAtomic, FileFd::Extension); + StateFile.Close(); + } + + // open it + if (!StateFile.Open(state, FileFd::ReadOnly, FileFd::Extension)) + return _error->Error(_("Failed to open StateFile %s"), + state.c_str()); + + FileFd OutFile(state, FileFd::ReadWrite | FileFd::Atomic, FileFd::Extension); + if (OutFile.IsOpen() == false || OutFile.Failed() == true) + return _error->Error(_("Failed to write temporary StateFile %s"), state.c_str()); + + // first merge with the existing sections + pkgTagFile tagfile(&StateFile); + pkgTagSection section; + std::set<string> pkgs_seen; + while(tagfile.Step(section)) { + string const pkgname = section.FindS("Package"); + string pkgarch = section.FindS("Architecture"); + if (pkgarch.empty() == true) + pkgarch = "native"; + // Silently ignore unknown packages and packages with no actual + // version. + pkgCache::PkgIterator pkg = Cache->FindPkg(pkgname, pkgarch); + if(pkg.end() || pkg.VersionList().end()) + continue; + StateCache const &P = PkgState[pkg->ID]; + bool newAuto = (P.Flags & Flag::Auto); + // reset to default (=manual) not installed or now-removed ones if requested + if (InstalledOnly && ( + (pkg->CurrentVer == 0 && P.Mode != ModeInstall) || + (pkg->CurrentVer != 0 && P.Mode == ModeDelete))) + newAuto = false; + if (newAuto == false) + { + // The section is obsolete if it contains no other tag + auto const count = section.Count(); + if (count < 2 || + (count == 2 && section.Exists("Auto-Installed")) || + (count == 3 && section.Exists("Auto-Installed") && section.Exists("Architecture"))) + { + if(debug_autoremove) + std::clog << "Drop obsolete section with " << count << " fields for " << APT::PrettyPkg(this, pkg) << std::endl; + continue; + } + } + + if(debug_autoremove) + std::clog << "Update existing AutoInstall to " << newAuto << " for " << APT::PrettyPkg(this, pkg) << std::endl; + + std::vector<pkgTagSection::Tag> rewrite; + rewrite.push_back(pkgTagSection::Tag::Rewrite("Architecture", pkg.Arch())); + rewrite.push_back(pkgTagSection::Tag::Rewrite("Auto-Installed", newAuto ? "1" : "0")); + section.Write(OutFile, NULL, rewrite); + if (OutFile.Write("\n", 1) == false) + return false; + pkgs_seen.insert(pkg.FullName()); + } + + // then write the ones we have not seen yet + for(pkgCache::PkgIterator pkg=Cache->PkgBegin(); !pkg.end(); ++pkg) { + StateCache const &P = PkgState[pkg->ID]; + if(P.Flags & Flag::Auto) { + if (pkgs_seen.find(pkg.FullName()) != pkgs_seen.end()) { + if(debug_autoremove) + std::clog << "Skipping already written " << APT::PrettyPkg(this, pkg) << std::endl; + continue; + } + // skip not installed ones if requested + if (InstalledOnly && ( + (pkg->CurrentVer == 0 && P.Mode != ModeInstall) || + (pkg->CurrentVer != 0 && P.Mode == ModeDelete))) + continue; + if(debug_autoremove) + std::clog << "Writing new AutoInstall: " << APT::PrettyPkg(this, pkg) << std::endl; + std::string stanza = "Package: "; + stanza.append(pkg.Name()) + .append("\nArchitecture: ").append(pkg.Arch()) + .append("\nAuto-Installed: 1\n\n"); + if (OutFile.Write(stanza.c_str(), stanza.length()) == false) + return false; + } + } + if (StateFile.Failed()) + { + OutFile.OpFail(); + return false; + } + if (OutFile.Close() == false) + return false; + chmod(state.c_str(), 0644); + return true; +} + /*}}}*/ +// DepCache::CheckDep - Checks a single dependency /*{{{*/ +// --------------------------------------------------------------------- +/* This first checks the dependency against the main target package and + then walks along the package provides list and checks if each provides + will be installed then checks the provides against the dep. Res will be + set to the package which was used to satisfy the dep. */ +bool pkgDepCache::CheckDep(DepIterator const &Dep,int const Type,PkgIterator &Res) +{ + Res = Dep.TargetPkg(); + + /* Check simple depends. A depends -should- never self match but + we allow it anyhow because dpkg does. Technically it is a packaging + bug. Conflicts may never self match */ + if (Dep.IsIgnorable(Res) == false) + { + // Check the base package + if (Type == NowVersion) + { + if (Res->CurrentVer != 0 && Dep.IsSatisfied(Res.CurrentVer()) == true) + return true; + } + else if (Type == InstallVersion) + { + if (PkgState[Res->ID].InstallVer != 0 && + Dep.IsSatisfied(PkgState[Res->ID].InstVerIter(*this)) == true) + return true; + } + else if (Type == CandidateVersion) + if (PkgState[Res->ID].CandidateVer != 0 && + Dep.IsSatisfied(PkgState[Res->ID].CandidateVerIter(*this)) == true) + return true; + } + + if (Dep->Type == Dep::Obsoletes) + return false; + + // Check the providing packages + PrvIterator P = Dep.TargetPkg().ProvidesList(); + for (; P.end() != true; ++P) + { + if (Dep.IsIgnorable(P) == true) + continue; + + // Check if the provides is a hit + if (Type == NowVersion) + { + if (P.OwnerPkg().CurrentVer() != P.OwnerVer()) + continue; + } + else if (Type == InstallVersion) + { + StateCache &State = PkgState[P.OwnerPkg()->ID]; + if (State.InstallVer != (Version *)P.OwnerVer()) + continue; + } + else if (Type == CandidateVersion) + { + StateCache &State = PkgState[P.OwnerPkg()->ID]; + if (State.CandidateVer != (Version *)P.OwnerVer()) + continue; + } + + // Compare the versions. + if (Dep.IsSatisfied(P) == true) + { + Res = P.OwnerPkg(); + return true; + } + } + + return false; +} + /*}}}*/ +// DepCache::AddSizes - Add the packages sizes to the counters /*{{{*/ +// --------------------------------------------------------------------- +/* Call with Inverse = true to perform the inverse operation */ +void pkgDepCache::AddSizes(const PkgIterator &Pkg, bool const Inverse) +{ + StateCache &P = PkgState[Pkg->ID]; + + if (Pkg->VersionList == 0) + return; + + if (Pkg.State() == pkgCache::PkgIterator::NeedsConfigure && + P.Keep() == true) + return; + + // Compute the size data + if (P.NewInstall() == true) + { + if (Inverse == false) { + iUsrSize += P.InstVerIter(*this)->InstalledSize; + iDownloadSize += P.InstVerIter(*this)->Size; + } else { + iUsrSize -= P.InstVerIter(*this)->InstalledSize; + iDownloadSize -= P.InstVerIter(*this)->Size; + } + return; + } + + // Upgrading + if (Pkg->CurrentVer != 0 && + (P.InstallVer != (Version *)Pkg.CurrentVer() || + (P.iFlags & ReInstall) == ReInstall) && P.InstallVer != 0) + { + if (Inverse == false) { + iUsrSize -= Pkg.CurrentVer()->InstalledSize; + iUsrSize += P.InstVerIter(*this)->InstalledSize; + iDownloadSize += P.InstVerIter(*this)->Size; + } else { + iUsrSize -= P.InstVerIter(*this)->InstalledSize; + iUsrSize += Pkg.CurrentVer()->InstalledSize; + iDownloadSize -= P.InstVerIter(*this)->Size; + } + return; + } + + // Reinstall + if (Pkg.State() == pkgCache::PkgIterator::NeedsUnpack && + P.Delete() == false) + { + if (Inverse == false) + iDownloadSize += P.InstVerIter(*this)->Size; + else + iDownloadSize -= P.InstVerIter(*this)->Size; + return; + } + + // Removing + if (Pkg->CurrentVer != 0 && P.InstallVer == 0) + { + if (Inverse == false) + iUsrSize -= Pkg.CurrentVer()->InstalledSize; + else + iUsrSize += Pkg.CurrentVer()->InstalledSize; + return; + } +} + /*}}}*/ +// DepCache::AddStates - Add the package to the state counter /*{{{*/ +// --------------------------------------------------------------------- +/* This routine is tricky to use, you must make sure that it is never + called twice for the same package. This means the Remove/Add section + should be as short as possible and not encompass any code that will + call Remove/Add itself. Remember, dependencies can be circular so + while processing a dep for Pkg it is possible that Add/Remove + will be called on Pkg */ +void pkgDepCache::AddStates(const PkgIterator &Pkg, bool const Invert) +{ + signed char const Add = (Invert == false) ? 1 : -1; + StateCache &State = PkgState[Pkg->ID]; + + // The Package is broken (either minimal dep or policy dep) + if ((State.DepState & DepInstMin) != DepInstMin) + iBrokenCount += Add; + if ((State.DepState & DepInstPolicy) != DepInstPolicy) + iPolicyBrokenCount += Add; + + // Bad state + if (Pkg.State() != PkgIterator::NeedsNothing) + iBadCount += Add; + + // Not installed + if (Pkg->CurrentVer == 0) + { + if (State.Mode == ModeDelete && + (State.iFlags & Purge) == Purge && Pkg.Purge() == false) + iDelCount += Add; + + if (State.Mode == ModeInstall) + iInstCount += Add; + return; + } + + // Installed, no upgrade + if (State.Status == 0) + { + if (State.Mode == ModeDelete) + iDelCount += Add; + else + if ((State.iFlags & ReInstall) == ReInstall) + iInstCount += Add; + return; + } + + // Alll 3 are possible + if (State.Mode == ModeDelete) + iDelCount += Add; + else if (State.Mode == ModeKeep) + iKeepCount += Add; + else if (State.Mode == ModeInstall) + iInstCount += Add; +} + /*}}}*/ +// DepCache::BuildGroupOrs - Generate the Or group dep data /*{{{*/ +// --------------------------------------------------------------------- +/* The or group results are stored in the last item of the or group. This + allows easy detection of the state of a whole or'd group. */ +void pkgDepCache::BuildGroupOrs(VerIterator const &V) +{ + unsigned char Group = 0; + for (DepIterator D = V.DependsList(); D.end() != true; ++D) + { + // Build the dependency state. + unsigned char &State = DepState[D->ID]; + + /* Invert for Conflicts. We have to do this twice to get the + right sense for a conflicts group */ + if (D.IsNegative() == true) + State = ~State; + + // Add to the group if we are within an or.. + State &= 0x7; + Group |= State; + State |= Group << 3; + if ((D->CompareOp & Dep::Or) != Dep::Or) + Group = 0; + + // Invert for Conflicts + if (D.IsNegative() == true) + State = ~State; + } +} + /*}}}*/ +// DepCache::VersionState - Perform a pass over a dependency list /*{{{*/ +// --------------------------------------------------------------------- +/* This is used to run over a dependency list and determine the dep + state of the list, filtering it through both a Min check and a Policy + check. The return result will have SetMin/SetPolicy low if a check + fails. It uses the DepState cache for it's computations. */ +unsigned char pkgDepCache::VersionState(DepIterator D, unsigned char const Check, + unsigned char const SetMin, + unsigned char const SetPolicy) const +{ + unsigned char Dep = 0xFF; + while (D.end() != true) + { + // the last or-dependency has the state of all previous or'ed + DepIterator Start, End; + D.GlobOr(Start, End); + // ignore if we are called with Dep{Install,…} or DepG{Install,…} + // the later would be more correct, but the first is what we get + unsigned char const State = DepState[End->ID] | (DepState[End->ID] >> 3); + + // Minimum deps that must be satisfied to have a working package + if (Start.IsCritical() == true) + { + if ((State & Check) != Check) + return Dep &= ~(SetMin | SetPolicy); + } + // Policy deps that must be satisfied to install the package + else if (IsImportantDep(Start) == true && + (State & Check) != Check) + Dep &= ~SetPolicy; + } + return Dep; +} + /*}}}*/ +// DepCache::DependencyState - Compute the 3 results for a dep /*{{{*/ +// --------------------------------------------------------------------- +/* This is the main dependency computation bit. It computes the 3 main + results for a dependency: Now, Install and Candidate. Callers must + invert the result if dealing with conflicts. */ +unsigned char pkgDepCache::DependencyState(DepIterator const &D) +{ + unsigned char State = 0; + + if (CheckDep(D,NowVersion) == true) + State |= DepNow; + if (CheckDep(D,InstallVersion) == true) + State |= DepInstall; + if (CheckDep(D,CandidateVersion) == true) + State |= DepCVer; + + return State; +} + /*}}}*/ +// DepCache::UpdateVerState - Compute the Dep member of the state /*{{{*/ +// --------------------------------------------------------------------- +/* This determines the combined dependency representation of a package + for its two states now and install. This is done by using the pre-generated + dependency information. */ +void pkgDepCache::UpdateVerState(PkgIterator const &Pkg) +{ + // Empty deps are always true + StateCache &State = PkgState[Pkg->ID]; + State.DepState = 0xFF; + + // Check the Current state + if (Pkg->CurrentVer != 0) + { + DepIterator D = Pkg.CurrentVer().DependsList(); + State.DepState &= VersionState(D,DepNow,DepNowMin,DepNowPolicy); + } + + /* Check the candidate state. We do not compare against the whole as + a candidate state but check the candidate version against the + install states */ + if (State.CandidateVer != 0) + { + DepIterator D = State.CandidateVerIter(*this).DependsList(); + State.DepState &= VersionState(D,DepInstall,DepCandMin,DepCandPolicy); + } + + // Check target state which can only be current or installed + if (State.InstallVer != 0) + { + DepIterator D = State.InstVerIter(*this).DependsList(); + State.DepState &= VersionState(D,DepInstall,DepInstMin,DepInstPolicy); + } +} + /*}}}*/ +// DepCache::Update - Figure out all the state information /*{{{*/ +// --------------------------------------------------------------------- +/* This will figure out the state of all the packages and all the + dependencies based on the current policy. */ +void pkgDepCache::Update(OpProgress * const Prog) +{ + iUsrSize = 0; + iDownloadSize = 0; + iInstCount = 0; + iDelCount = 0; + iKeepCount = 0; + iBrokenCount = 0; + iPolicyBrokenCount = 0; + iBadCount = 0; + + // Perform the depends pass + int Done = 0; + for (PkgIterator I = PkgBegin(); I.end() != true; ++I, ++Done) + { + if (Prog != 0 && Done%20 == 0) + Prog->Progress(Done); + for (VerIterator V = I.VersionList(); V.end() != true; ++V) + { + unsigned char Group = 0; + + for (DepIterator D = V.DependsList(); D.end() != true; ++D) + { + // Build the dependency state. + unsigned char &State = DepState[D->ID]; + State = DependencyState(D); + + // Add to the group if we are within an or.. + Group |= State; + State |= Group << 3; + if ((D->CompareOp & Dep::Or) != Dep::Or) + Group = 0; + + // Invert for Conflicts + if (D.IsNegative() == true) + State = ~State; + } + } + + // Compute the package dependency state and size additions + AddSizes(I); + UpdateVerState(I); + AddStates(I); + } + + if (Prog != 0) + Prog->Progress(Done); + + readStateFile(Prog); +} + /*}}}*/ +// DepCache::Update - Update the deps list of a package /*{{{*/ +// --------------------------------------------------------------------- +/* This is a helper for update that only does the dep portion of the scan. + It is mainly meant to scan reverse dependencies. */ +void pkgDepCache::Update(DepIterator D) +{ + // Update the reverse deps + for (;D.end() != true; ++D) + { + unsigned char &State = DepState[D->ID]; + State = DependencyState(D); + + // Invert for Conflicts + if (D.IsNegative() == true) + State = ~State; + + RemoveStates(D.ParentPkg()); + BuildGroupOrs(D.ParentVer()); + UpdateVerState(D.ParentPkg()); + AddStates(D.ParentPkg()); + } +} + /*}}}*/ +// DepCache::Update - Update the related deps of a package /*{{{*/ +// --------------------------------------------------------------------- +/* This is called whenever the state of a package changes. It updates + all cached dependencies related to this package. */ +void pkgDepCache::Update(PkgIterator const &Pkg) +{ + // Recompute the dep of the package + RemoveStates(Pkg); + UpdateVerState(Pkg); + AddStates(Pkg); + + // Update the reverse deps + Update(Pkg.RevDependsList()); + + // Update the provides map for the current ver + if (Pkg->CurrentVer != 0) + for (PrvIterator P = Pkg.CurrentVer().ProvidesList(); + P.end() != true; ++P) + Update(P.ParentPkg().RevDependsList()); + + // Update the provides map for the candidate ver + if (PkgState[Pkg->ID].CandidateVer != 0) + for (PrvIterator P = PkgState[Pkg->ID].CandidateVerIter(*this).ProvidesList(); + P.end() != true; ++P) + Update(P.ParentPkg().RevDependsList()); +} + /*}}}*/ +// DepCache::MarkKeep - Put the package in the keep state /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgDepCache::MarkKeep(PkgIterator const &Pkg, bool Soft, bool FromUser, + unsigned long Depth) +{ + if (IsModeChangeOk(ModeKeep, Pkg, Depth, FromUser) == false) + return false; + + /* Reject an attempt to keep a non-source broken installed package, those + must be upgraded */ + if (Pkg.State() == PkgIterator::NeedsUnpack && + Pkg.CurrentVer().Downloadable() == false) + return false; + + /* We changed the soft state all the time so the UI is a bit nicer + to use */ + StateCache &P = PkgState[Pkg->ID]; + + // Check that it is not already kept + if (P.Mode == ModeKeep) + return true; + + if (Soft == true) + P.iFlags |= AutoKept; + else + P.iFlags &= ~AutoKept; + + ActionGroup group(*this); + +#if 0 // resetting the autoflag here means we lose the + // auto-mark information if a user selects a package for removal + // but changes his mind then and sets it for keep again + // - this makes sense as default when all Garbage dependencies + // are automatically marked for removal (as aptitude does). + // setting a package for keep then makes it no longer autoinstalled + // for all other use-case this action is rather surprising + if(FromUser && !P.Marked) + P.Flags &= ~Flag::Auto; +#endif + + if (DebugMarker == true) + std::clog << OutputInDepth(Depth) << "MarkKeep " << APT::PrettyPkg(this, Pkg) << " FU=" << FromUser << std::endl; + + RemoveSizes(Pkg); + RemoveStates(Pkg); + + P.Mode = ModeKeep; + if (Pkg->CurrentVer == 0) + P.InstallVer = 0; + else + P.InstallVer = Pkg.CurrentVer(); + + AddStates(Pkg); + Update(Pkg); + AddSizes(Pkg); + + return true; +} + /*}}}*/ +// DepCache::MarkDelete - Put the package in the delete state /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgDepCache::MarkDelete(PkgIterator const &Pkg, bool rPurge, + unsigned long Depth, bool FromUser) +{ + if (IsModeChangeOk(ModeDelete, Pkg, Depth, FromUser) == false) + return false; + + StateCache &P = PkgState[Pkg->ID]; + + // Check that it is not already marked for delete + if ((P.Mode == ModeDelete || P.InstallVer == 0) && + (Pkg.Purge() == true || rPurge == false)) + return true; + + // check if we are allowed to remove the package + if (IsDeleteOk(Pkg,rPurge,Depth,FromUser) == false) + return false; + + P.iFlags &= ~(AutoKept | Purge); + if (rPurge == true) + P.iFlags |= Purge; + + ActionGroup group(*this); + + if (FromUser == false) + { + VerIterator const PV = P.InstVerIter(*this); + if (PV.end() == false) + { + // removed metapackages mark their dependencies as manual to prevent in "desktop depends browser, texteditor" + // the removal of browser to suggest the removal of desktop and texteditor. + // We ignore the auto-bit here as we can't deal with metapackage cascardes otherwise. + // We do not check for or-groups here as we don't know which package takes care of + // providing the feature the user likes e.g.: browser1 | browser2 | browser3 + // Temporary removals are effected by this as well, which is bad, but unlikely in practice + bool const PinNeverMarkAutoSection = (PV->Section != 0 && ConfigValueInSubTree("APT::Never-MarkAuto-Sections", PV.Section())); + if (PinNeverMarkAutoSection) + { + for (DepIterator D = PV.DependsList(); D.end() != true; ++D) + { + if (D.IsMultiArchImplicit() == true || D.IsNegative() == true || IsImportantDep(D) == false) + continue; + + pkgCacheFile CacheFile(this); + APT::VersionList verlist = APT::VersionList::FromDependency(CacheFile, D, APT::CacheSetHelper::INSTALLED); + for (auto const &V : verlist) + { + PkgIterator const DP = V.ParentPkg(); + if(DebugAutoInstall == true) + std::clog << OutputInDepth(Depth) << "Setting " << DP.FullName(false) << " NOT as auto-installed (direct " + << D.DepType() << " of " << Pkg.FullName(false) << " which is in APT::Never-MarkAuto-Sections)" << std::endl; + + MarkAuto(DP, false); + } + } + } + } + } + + if (DebugMarker == true) + std::clog << OutputInDepth(Depth) << (rPurge ? "MarkPurge " : "MarkDelete ") << APT::PrettyPkg(this, Pkg) << " FU=" << FromUser << std::endl; + + RemoveSizes(Pkg); + RemoveStates(Pkg); + + if (Pkg->CurrentVer == 0 && (Pkg.Purge() == true || rPurge == false)) + P.Mode = ModeKeep; + else + P.Mode = ModeDelete; + P.InstallVer = 0; + + AddStates(Pkg); + Update(Pkg); + AddSizes(Pkg); + + return true; +} + /*}}}*/ +// DepCache::IsDeleteOk - check if it is ok to remove this package /*{{{*/ +// --------------------------------------------------------------------- +/* The default implementation tries to prevent deletion of install requests. + dpkg holds are enforced by the private IsModeChangeOk */ +bool pkgDepCache::IsDeleteOk(PkgIterator const &Pkg,bool rPurge, + unsigned long Depth, bool FromUser) +{ + return IsDeleteOkProtectInstallRequests(Pkg, rPurge, Depth, FromUser); +} +bool pkgDepCache::IsDeleteOkProtectInstallRequests(PkgIterator const &Pkg, + bool const /*rPurge*/, unsigned long const Depth, bool const FromUser) +{ + if (FromUser == false && Pkg->CurrentVer == 0) + { + StateCache &P = PkgState[Pkg->ID]; + if (P.InstallVer != 0 && P.Status == 2 && (P.Flags & Flag::Auto) != Flag::Auto) + { + if (DebugMarker == true) + std::clog << OutputInDepth(Depth) << "Manual install request prevents MarkDelete of " << APT::PrettyPkg(this, Pkg) << std::endl; + return false; + } + } + return true; +} + /*}}}*/ +// DepCache::IsModeChangeOk - check if it is ok to change the mode /*{{{*/ +// --------------------------------------------------------------------- +/* this is used by all Mark methods on the very first line to check sanity + and prevents mode changes for packages on hold for example. + If you want to check Mode specific stuff you can use the virtual public + Is<Mode>Ok methods instead */ +static char const* PrintMode(char const mode) +{ + switch (mode) + { + case pkgDepCache::ModeInstall: return "Install"; + case pkgDepCache::ModeKeep: return "Keep"; + case pkgDepCache::ModeDelete: return "Delete"; + case pkgDepCache::ModeGarbage: return "Garbage"; + default: return "UNKNOWN"; + } +} +bool pkgDepCache::IsModeChangeOk(ModeList const mode, PkgIterator const &Pkg, + unsigned long const Depth, bool const FromUser) +{ + // we are not trying to hard… + if (unlikely(Depth > 100)) + return false; + + // general sanity + if (unlikely(Pkg.end() == true || Pkg->VersionList == 0)) + return false; + + // the user is always right + if (FromUser == true) + return true; + + StateCache &P = PkgState[Pkg->ID]; + // not changing the mode is obviously also fine as we might want to call + // e.g. MarkInstall multiple times with different arguments for the same package + if (P.Mode == mode) + return true; + + // if previous state was set by user only user can reset it + if ((P.iFlags & Protected) == Protected) + { + if (unlikely(DebugMarker == true)) + std::clog << OutputInDepth(Depth) << "Ignore Mark" << PrintMode(mode) + << " of " << APT::PrettyPkg(this, Pkg) << " as its mode (" << PrintMode(P.Mode) + << ") is protected" << std::endl; + return false; + } + // enforce dpkg holds + else if (mode != ModeKeep && Pkg->SelectedState == pkgCache::State::Hold && + _config->FindB("APT::Ignore-Hold",false) == false) + { + if (unlikely(DebugMarker == true)) + std::clog << OutputInDepth(Depth) << "Hold prevents Mark" << PrintMode(mode) + << " of " << APT::PrettyPkg(this, Pkg) << std::endl; + return false; + } + + return true; +} + /*}}}*/ +// DepCache::MarkInstall - Put the package in the install state /*{{{*/ +// --------------------------------------------------------------------- +/* */ +struct CompareProviders { + pkgCache::PkgIterator const Pkg; + explicit CompareProviders(pkgCache::DepIterator const &Dep) : Pkg(Dep.TargetPkg()) {}; + //bool operator() (APT::VersionList::iterator const &AV, APT::VersionList::iterator const &BV) + bool operator() (pkgCache::VerIterator const &AV, pkgCache::VerIterator const &BV) + { + pkgCache::PkgIterator const A = AV.ParentPkg(); + pkgCache::PkgIterator const B = BV.ParentPkg(); + // Prefer MA:same packages if other architectures for it are installed + if ((AV->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same || + (BV->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same) + { + bool instA = false; + if ((AV->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same) + { + pkgCache::GrpIterator Grp = A.Group(); + for (pkgCache::PkgIterator P = Grp.PackageList(); P.end() == false; P = Grp.NextPkg(P)) + if (P->CurrentVer != 0) + { + instA = true; + break; + } + } + bool instB = false; + if ((BV->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same) + { + pkgCache::GrpIterator Grp = B.Group(); + for (pkgCache::PkgIterator P = Grp.PackageList(); P.end() == false; P = Grp.NextPkg(P)) + { + if (P->CurrentVer != 0) + { + instB = true; + break; + } + } + } + if (instA != instB) + return instA == false; + } + if ((A->CurrentVer == 0 || B->CurrentVer == 0) && A->CurrentVer != B->CurrentVer) + return A->CurrentVer == 0; + // Prefer packages in the same group as the target; e.g. foo:i386, foo:amd64 + if (A->Group != B->Group) + { + if (A->Group == Pkg->Group && B->Group != Pkg->Group) + return false; + else if (B->Group == Pkg->Group && A->Group != Pkg->Group) + return true; + } + // we like essentials + if ((A->Flags & pkgCache::Flag::Essential) != (B->Flags & pkgCache::Flag::Essential)) + { + if ((A->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential) + return false; + else if ((B->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential) + return true; + } + if ((A->Flags & pkgCache::Flag::Important) != (B->Flags & pkgCache::Flag::Important)) + { + if ((A->Flags & pkgCache::Flag::Important) == pkgCache::Flag::Important) + return false; + else if ((B->Flags & pkgCache::Flag::Important) == pkgCache::Flag::Important) + return true; + } + // prefer native architecture + if (strcmp(A.Arch(), B.Arch()) != 0) + { + if (strcmp(A.Arch(), A.Cache()->NativeArch()) == 0) + return false; + else if (strcmp(B.Arch(), B.Cache()->NativeArch()) == 0) + return true; + std::vector<std::string> archs = APT::Configuration::getArchitectures(); + for (std::vector<std::string>::const_iterator a = archs.begin(); a != archs.end(); ++a) + if (*a == A.Arch()) + return false; + else if (*a == B.Arch()) + return true; + } + // higher priority seems like a good idea + if (AV->Priority != BV->Priority) + return AV->Priority > BV->Priority; + // unable to decide… + return A->ID < B->ID; + } +}; +bool pkgDepCache::MarkInstall(PkgIterator const &Pkg,bool AutoInst, + unsigned long Depth, bool FromUser, + bool ForceImportantDeps) +{ + if (IsModeChangeOk(ModeInstall, Pkg, Depth, FromUser) == false) + return false; + + StateCache &P = PkgState[Pkg->ID]; + + // See if there is even any possible installation candidate + if (P.CandidateVer == 0) + return false; + + /* Check that it is not already marked for install and that it can be + installed */ + if ((P.InstPolicyBroken() == false && P.InstBroken() == false) && + (P.Mode == ModeInstall || + P.CandidateVer == (Version *)Pkg.CurrentVer())) + { + if (P.CandidateVer == (Version *)Pkg.CurrentVer() && P.InstallVer == 0) + return MarkKeep(Pkg, false, FromUser, Depth+1); + return true; + } + + // check if we are allowed to install the package + if (IsInstallOk(Pkg,AutoInst,Depth,FromUser) == false) + return false; + + ActionGroup group(*this); + P.iFlags &= ~AutoKept; + + /* Target the candidate version and remove the autoflag. We reset the + autoflag below if this was called recursively. Otherwise the user + should have the ability to de-auto a package by changing its state */ + RemoveSizes(Pkg); + RemoveStates(Pkg); + + P.Mode = ModeInstall; + P.InstallVer = P.CandidateVer; + + if(FromUser) + { + // Set it to manual if it's a new install or already installed, + // but only if its not marked by the autoremover (aptitude depend on this behavior) + // or if we do automatic installation (aptitude never does it) + if(P.Status == 2 || (Pkg->CurrentVer != 0 && (AutoInst == true || P.Marked == false))) + P.Flags &= ~Flag::Auto; + } + else + { + // Set it to auto if this is a new install. + if(P.Status == 2) + P.Flags |= Flag::Auto; + } + if (P.CandidateVer == (Version *)Pkg.CurrentVer()) + P.Mode = ModeKeep; + + AddStates(Pkg); + Update(Pkg); + AddSizes(Pkg); + + if (AutoInst == false || _config->Find("APT::Solver", "internal") != "internal") + return true; + + if (DebugMarker == true) + std::clog << OutputInDepth(Depth) << "MarkInstall " << APT::PrettyPkg(this, Pkg) << " FU=" << FromUser << std::endl; + + bool MoveAutoBitToDependencies = false; + VerIterator const PV = P.InstVerIter(*this); + if (unlikely(PV.end() == true)) + return false; + else if (PV->Section != 0 && (P.Flags & Flag::Auto) != Flag::Auto) + { + VerIterator const CurVer = Pkg.CurrentVer(); + if (CurVer.end() == false && CurVer->Section != 0 && strcmp(CurVer.Section(), PV.Section()) != 0) + { + bool const CurVerInMoveSection = ConfigValueInSubTree("APT::Move-Autobit-Sections", CurVer.Section()); + bool const InstVerInMoveSection = ConfigValueInSubTree("APT::Move-Autobit-Sections", PV.Section()); + MoveAutoBitToDependencies = (CurVerInMoveSection == false && InstVerInMoveSection == true); + if (MoveAutoBitToDependencies == true) + { + if(DebugAutoInstall == true) + std::clog << OutputInDepth(Depth) << "Setting " << Pkg.FullName(false) << " as auto-installed, moving manual to its dependencies" << std::endl; + MarkAuto(Pkg, true); + } + } + } + + DepIterator Dep = PV.DependsList(); + for (; Dep.end() != true;) + { + // Grok or groups + DepIterator Start = Dep; + bool Result = true; + unsigned Ors = 0; + for (bool LastOR = true; Dep.end() == false && LastOR == true; ++Dep, ++Ors) + { + LastOR = (Dep->CompareOp & Dep::Or) == Dep::Or; + + if ((DepState[Dep->ID] & DepInstall) == DepInstall) + Result = false; + } + + // Dep is satisfied okay. + if (Result == false) + continue; + + /* Check if this dep should be consider for install. If it is a user + defined important dep and we are installed a new package then + it will be installed. Otherwise we only check for important + deps that have changed from the installed version */ + if (IsImportantDep(Start) == false) + continue; + + /* If we are in an or group locate the first or that can + succeed. We have already cached this… */ + for (; Ors > 1 && (DepState[Start->ID] & DepCVer) != DepCVer; --Ors) + ++Start; + + /* unsatisfiable dependency: IsInstallOkDependenciesSatisfiableByCandidates + would have prevented us to get here if not overridden, so just skip + over the problem here as the front-end will know what it is doing */ + if (Ors == 1 && (DepState[Start->ID] &DepCVer) != DepCVer && Start.IsNegative() == false) + continue; + + /* Check if any ImportantDep() (but not Critical) were added + * since we installed the package. Also check for deps that + * were satisfied in the past: for instance, if a version + * restriction in a Recommends was tightened, upgrading the + * package should follow that Recommends rather than causing the + * dependency to be removed. (bug #470115) + */ + if (Pkg->CurrentVer != 0 && ForceImportantDeps == false && Start.IsCritical() == false) + { + bool isNewImportantDep = true; + bool isPreviouslySatisfiedImportantDep = false; + for (DepIterator D = Pkg.CurrentVer().DependsList(); D.end() != true; ++D) + { + //FIXME: Should we handle or-group better here? + // We do not check if the package we look for is part of the same or-group + // we might find while searching, but could that really be a problem? + if (D.IsCritical() == true || IsImportantDep(D) == false || + Start.TargetPkg() != D.TargetPkg()) + continue; + + isNewImportantDep = false; + + while ((D->CompareOp & Dep::Or) != 0) + ++D; + + isPreviouslySatisfiedImportantDep = (((*this)[D] & DepGNow) != 0); + if (isPreviouslySatisfiedImportantDep == true) + break; + } + + if(isNewImportantDep == true) + { + if (DebugAutoInstall == true) + std::clog << OutputInDepth(Depth) << "new important dependency: " + << Start.TargetPkg().FullName() << std::endl; + } + else if(isPreviouslySatisfiedImportantDep == true) + { + if (DebugAutoInstall == true) + std::clog << OutputInDepth(Depth) << "previously satisfied important dependency on " + << Start.TargetPkg().FullName() << std::endl; + } + else + { + if (DebugAutoInstall == true) + std::clog << OutputInDepth(Depth) << "ignore old unsatisfied important dependency on " + << Start.TargetPkg().FullName() << std::endl; + continue; + } + } + + /* This bit is for processing the possibility of an install/upgrade + fixing the problem for "positive" dependencies */ + if (Start.IsNegative() == false && (DepState[Start->ID] & DepCVer) == DepCVer) + { + pkgCacheFile CacheFile(this); + APT::VersionList verlist = APT::VersionList::FromDependency(CacheFile, Start, APT::CacheSetHelper::CANDIDATE); + CompareProviders comp(Start); + + do { + APT::VersionList::iterator InstVer = std::max_element(verlist.begin(), verlist.end(), comp); + + if (InstVer == verlist.end()) + break; + + pkgCache::PkgIterator InstPkg = InstVer.ParentPkg(); + if(DebugAutoInstall == true) + std::clog << OutputInDepth(Depth) << "Installing " << InstPkg.Name() + << " as " << Start.DepType() << " of " << Pkg.Name() + << std::endl; + if (MarkInstall(InstPkg, true, Depth + 1, false, ForceImportantDeps) == false) + { + verlist.erase(InstVer); + continue; + } + + // now check if we should consider it a automatic dependency or not + if(InstPkg->CurrentVer == 0 && MoveAutoBitToDependencies) + { + if(DebugAutoInstall == true) + std::clog << OutputInDepth(Depth) << "Setting " << InstPkg.FullName(false) << " NOT as auto-installed (direct " + << Start.DepType() << " of " << Pkg.FullName(false) << " which is manual and in APT::Move-Autobit-Sections)" << std::endl; + MarkAuto(InstPkg, false); + } + + + break; + } while(true); + continue; + } + /* Negative dependencies have no or-group + If the dependency isn't versioned, we try if an upgrade might solve the problem. + Otherwise we remove the offender if needed */ + else if (Start.IsNegative() == true && Start->Type != pkgCache::Dep::Obsoletes) + { + std::unique_ptr<Version *[]> List(Start.AllTargets()); + pkgCache::PkgIterator TrgPkg = Start.TargetPkg(); + for (Version **I = List.get(); *I != 0; I++) + { + VerIterator Ver(*this,*I); + PkgIterator Pkg = Ver.ParentPkg(); + + /* The List includes all packages providing this dependency, + even providers which are not installed, so skip them. */ + if (PkgState[Pkg->ID].InstallVer == 0) + continue; + + /* Ignore negative dependencies that we are not going to + get installed */ + if (PkgState[Pkg->ID].InstallVer != *I) + continue; + + if ((Start->Version != 0 || TrgPkg != Pkg) && + PkgState[Pkg->ID].CandidateVer != PkgState[Pkg->ID].InstallVer && + PkgState[Pkg->ID].CandidateVer != *I && + MarkInstall(Pkg,true,Depth + 1, false, ForceImportantDeps) == true) + continue; + else if (Start->Type == pkgCache::Dep::Conflicts || + Start->Type == pkgCache::Dep::DpkgBreaks) + { + if(DebugAutoInstall == true) + std::clog << OutputInDepth(Depth) + << " Removing: " << Pkg.Name() + << std::endl; + if (MarkDelete(Pkg,false,Depth + 1, false) == false) + break; + } + } + continue; + } + } + + return Dep.end() == true; +} + /*}}}*/ +// DepCache::IsInstallOk - check if it is ok to install this package /*{{{*/ +// --------------------------------------------------------------------- +/* The default implementation checks if the installation of an M-A:same + package would lead us into a version-screw and if so forbids it. + dpkg holds are enforced by the private IsModeChangeOk */ +bool pkgDepCache::IsInstallOk(PkgIterator const &Pkg,bool AutoInst, + unsigned long Depth, bool FromUser) +{ + return IsInstallOkMultiArchSameVersionSynced(Pkg,AutoInst, Depth, FromUser) && + IsInstallOkDependenciesSatisfiableByCandidates(Pkg,AutoInst, Depth, FromUser); +} +bool pkgDepCache::IsInstallOkMultiArchSameVersionSynced(PkgIterator const &Pkg, + bool const /*AutoInst*/, unsigned long const Depth, bool const FromUser) +{ + if (FromUser == true) // as always: user is always right + return true; + + // if we have checked before and it was okay, it will still be okay + if (PkgState[Pkg->ID].Mode == ModeInstall && + PkgState[Pkg->ID].InstallVer == PkgState[Pkg->ID].CandidateVer) + return true; + + // ignore packages with none-M-A:same candidates + VerIterator const CandVer = PkgState[Pkg->ID].CandidateVerIter(*this); + if (unlikely(CandVer.end() == true) || CandVer == Pkg.CurrentVer() || + (CandVer->MultiArch & pkgCache::Version::Same) != pkgCache::Version::Same) + return true; + + GrpIterator const Grp = Pkg.Group(); + for (PkgIterator P = Grp.PackageList(); P.end() == false; P = Grp.NextPkg(P)) + { + // not installed or self-check: fine by definition + if (P->CurrentVer == 0 || P == Pkg) + continue; + + // not having a candidate or being in sync + // (simple string-compare as stuff like '1' == '0:1-0' can't happen here) + VerIterator CV = PkgState[P->ID].CandidateVerIter(*this); + if (CV.end() == true || strcmp(CandVer.VerStr(), CV.VerStr()) == 0) + continue; + + // packages losing M-A:same can be out-of-sync + if ((CV->MultiArch & pkgCache::Version::Same) != pkgCache::Version::Same) + continue; + + // not downloadable means the package is obsolete, so allow out-of-sync + if (CV.Downloadable() == false) + continue; + + PkgState[Pkg->ID].iFlags |= AutoKept; + if (unlikely(DebugMarker == true)) + std::clog << OutputInDepth(Depth) << "Ignore MarkInstall of " << APT::PrettyPkg(this, Pkg) + << " as it is not in sync with its M-A:same sibling " << APT::PrettyPkg(this, P) + << " (" << CandVer.VerStr() << " != " << CV.VerStr() << ")" << std::endl; + return false; + } + + return true; +} +bool pkgDepCache::IsInstallOkDependenciesSatisfiableByCandidates(PkgIterator const &Pkg, + bool const AutoInst, unsigned long const Depth, bool const /*FromUser*/) +{ + if (AutoInst == false) + return true; + + VerIterator const CandVer = PkgState[Pkg->ID].CandidateVerIter(*this); + if (unlikely(CandVer.end() == true) || CandVer == Pkg.CurrentVer()) + return true; + + for (DepIterator Dep = CandVer.DependsList(); Dep.end() != true;) + { + // Grok or groups + DepIterator Start = Dep; + bool Result = true; + unsigned Ors = 0; + for (bool LastOR = true; Dep.end() == false && LastOR == true; ++Dep, ++Ors) + { + LastOR = (Dep->CompareOp & Dep::Or) == Dep::Or; + + if ((DepState[Dep->ID] & DepInstall) == DepInstall) + Result = false; + } + + if (Start.IsCritical() == false || Start.IsNegative() == true || Result == false) + continue; + + /* If we are in an or group locate the first or that can succeed. + We have already cached this… */ + for (; Ors > 1 && (DepState[Start->ID] & DepCVer) != DepCVer; --Ors) + ++Start; + + if (Ors == 1 && (DepState[Start->ID] &DepCVer) != DepCVer) + { + if (DebugAutoInstall == true) + std::clog << OutputInDepth(Depth) << APT::PrettyDep(this, Start) << " can't be satisfied!" << std::endl; + + // the dependency is critical, but can't be installed, so discard the candidate + // as the problemresolver will trip over it otherwise trying to install it (#735967) + if (Pkg->CurrentVer != 0 && (PkgState[Pkg->ID].iFlags & Protected) != Protected) + { + SetCandidateVersion(Pkg.CurrentVer()); + StateCache &State = PkgState[Pkg->ID]; + if (State.Mode != ModeDelete) + { + State.Mode = ModeKeep; + State.Update(Pkg, *this); + } + } + return false; + } + } + + return true; +} + /*}}}*/ +// DepCache::SetReInstall - Set the reinstallation flag /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgDepCache::SetReInstall(PkgIterator const &Pkg,bool To) +{ + if (unlikely(Pkg.end() == true)) + return; + + APT::PackageList pkglist; + if (Pkg->CurrentVer != 0 && + (Pkg.CurrentVer()-> MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same) + { + pkgCache::GrpIterator Grp = Pkg.Group(); + for (pkgCache::PkgIterator P = Grp.PackageList(); P.end() == false; P = Grp.NextPkg(P)) + { + if (P->CurrentVer != 0) + pkglist.insert(P); + } + } + else + pkglist.insert(Pkg); + + ActionGroup group(*this); + + for (APT::PackageList::const_iterator Pkg = pkglist.begin(); Pkg != pkglist.end(); ++Pkg) + { + RemoveSizes(Pkg); + RemoveStates(Pkg); + + StateCache &P = PkgState[Pkg->ID]; + if (To == true) + P.iFlags |= ReInstall; + else + P.iFlags &= ~ReInstall; + + AddStates(Pkg); + AddSizes(Pkg); + } +} + /*}}}*/ +pkgCache::VerIterator pkgDepCache::GetCandidateVersion(PkgIterator const &Pkg)/*{{{*/ +{ + return PkgState[Pkg->ID].CandidateVerIter(*this); +} + /*}}}*/ +// DepCache::SetCandidateVersion - Change the candidate version /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgDepCache::SetCandidateVersion(VerIterator TargetVer) +{ + pkgCache::PkgIterator Pkg = TargetVer.ParentPkg(); + StateCache &P = PkgState[Pkg->ID]; + + if (P.CandidateVer == TargetVer) + return; + + ActionGroup group(*this); + + RemoveSizes(Pkg); + RemoveStates(Pkg); + + if (P.CandidateVer == P.InstallVer && P.Install() == true) + P.InstallVer = (Version *)TargetVer; + P.CandidateVer = (Version *)TargetVer; + P.Update(Pkg,*this); + + AddStates(Pkg); + Update(Pkg); + AddSizes(Pkg); + +} + /*}}}*/ +// DepCache::SetCandidateRelease - Change the candidate version /*{{{*/ +// --------------------------------------------------------------------- +/* changes the candidate of a package and walks over all its dependencies + to check if it needs to change the candidate of the dependency, too, + to reach a installable versionstate */ +bool pkgDepCache::SetCandidateRelease(pkgCache::VerIterator TargetVer, + std::string const &TargetRel) +{ + std::list<std::pair<pkgCache::VerIterator, pkgCache::VerIterator> > Changed; + return SetCandidateRelease(TargetVer, TargetRel, Changed); +} +bool pkgDepCache::SetCandidateRelease(pkgCache::VerIterator TargetVer, + std::string const &TargetRel, + std::list<std::pair<pkgCache::VerIterator, pkgCache::VerIterator> > &Changed) +{ + ActionGroup group(*this); + SetCandidateVersion(TargetVer); + + if (TargetRel == "installed" || TargetRel == "candidate") // both doesn't make sense in this context + return true; + + pkgVersionMatch Match(TargetRel, pkgVersionMatch::Release); + // save the position of the last element we will not undo - if we have to + std::list<std::pair<pkgCache::VerIterator, pkgCache::VerIterator> >::iterator newChanged = --(Changed.end()); + + for (pkgCache::DepIterator D = TargetVer.DependsList(); D.end() == false; ++D) + { + if (D->Type != pkgCache::Dep::PreDepends && D->Type != pkgCache::Dep::Depends && + ((D->Type != pkgCache::Dep::Recommends && D->Type != pkgCache::Dep::Suggests) || + IsImportantDep(D) == false)) + continue; + + // walk over an or-group and check if we need to do anything + // for simpilicity no or-group is handled as a or-group including one dependency + pkgCache::DepIterator Start = D; + bool itsFine = false; + for (bool stillOr = true; stillOr == true; ++Start) + { + stillOr = (Start->CompareOp & Dep::Or) == Dep::Or; + pkgCache::PkgIterator const P = Start.TargetPkg(); + // virtual packages can't be a solution + if (P.end() == true || (P->ProvidesList == 0 && P->VersionList == 0)) + continue; + // if its already installed, check if this one is good enough + pkgCache::VerIterator const Now = P.CurrentVer(); + if (Now.end() == false && Start.IsSatisfied(Now)) + { + itsFine = true; + break; + } + pkgCache::VerIterator const Cand = PkgState[P->ID].CandidateVerIter(*this); + // no versioned dependency - but is it installable? + if (Start.TargetVer() == 0 || Start.TargetVer()[0] == '\0') + { + // Check if one of the providers is installable + if (P->ProvidesList != 0) + { + pkgCache::PrvIterator Prv = P.ProvidesList(); + for (; Prv.end() == false; ++Prv) + { + pkgCache::VerIterator const C = PkgState[Prv.OwnerPkg()->ID].CandidateVerIter(*this); + if (C.end() == true || C != Prv.OwnerVer() || + (VersionState(C.DependsList(), DepInstall, DepCandMin, DepCandPolicy) & DepCandMin) != DepCandMin) + continue; + break; + } + if (Prv.end() == true) + continue; + } + // no providers, so check if we have an installable candidate version + else if (Cand.end() == true || + (VersionState(Cand.DependsList(), DepInstall, DepCandMin, DepCandPolicy) & DepCandMin) != DepCandMin) + continue; + itsFine = true; + break; + } + if (Cand.end() == true) + continue; + // check if the current candidate is enough for the versioned dependency - and installable? + if (Start.IsSatisfied(Cand) == true && + (VersionState(Cand.DependsList(), DepInstall, DepCandMin, DepCandPolicy) & DepCandMin) == DepCandMin) + { + itsFine = true; + break; + } + } + + if (itsFine == true) { + // something in the or-group was fine, skip all other members + for (; (D->CompareOp & Dep::Or) == Dep::Or; ++D); + continue; + } + + // walk again over the or-group and check each if a candidate switch would help + itsFine = false; + for (bool stillOr = true; stillOr == true; ++D) + { + stillOr = (D->CompareOp & Dep::Or) == Dep::Or; + // changing candidate will not help if the dependency is not versioned + if (D.TargetVer() == 0 || D.TargetVer()[0] == '\0') + { + if (stillOr == true) + continue; + break; + } + + pkgCache::VerIterator V; + if (TargetRel == "newest") + V = D.TargetPkg().VersionList(); + else + V = Match.Find(D.TargetPkg()); + + // check if the version from this release could satisfy the dependency + if (V.end() == true || D.IsSatisfied(V) == false) + { + if (stillOr == true) + continue; + break; + } + + pkgCache::VerIterator oldCand = PkgState[D.TargetPkg()->ID].CandidateVerIter(*this); + if (V == oldCand) + { + // Do we already touched this Version? If so, their versioned dependencies are okay, no need to check again + for (std::list<std::pair<pkgCache::VerIterator, pkgCache::VerIterator> >::const_iterator c = Changed.begin(); + c != Changed.end(); ++c) + { + if (c->first->ParentPkg != V->ParentPkg) + continue; + itsFine = true; + break; + } + } + + if (itsFine == false) + { + // change the candidate + Changed.push_back(make_pair(V, TargetVer)); + if (SetCandidateRelease(V, TargetRel, Changed) == false) + { + if (stillOr == false) + break; + // undo the candidate changing + SetCandidateVersion(oldCand); + Changed.pop_back(); + continue; + } + itsFine = true; + } + + // something in the or-group was fine, skip all other members + for (; (D->CompareOp & Dep::Or) == Dep::Or; ++D); + break; + } + + if (itsFine == false && (D->Type == pkgCache::Dep::PreDepends || D->Type == pkgCache::Dep::Depends)) + { + // undo all changes which aren't lead to a solution + for (std::list<std::pair<pkgCache::VerIterator, pkgCache::VerIterator> >::const_iterator c = ++newChanged; + c != Changed.end(); ++c) + SetCandidateVersion(c->first); + Changed.erase(newChanged, Changed.end()); + return false; + } + } + return true; +} + /*}}}*/ +// DepCache::MarkAuto - set the Auto flag for a package /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgDepCache::MarkAuto(const PkgIterator &Pkg, bool Auto) +{ + StateCache &state = PkgState[Pkg->ID]; + + ActionGroup group(*this); + + if(Auto) + state.Flags |= Flag::Auto; + else + state.Flags &= ~Flag::Auto; +} + /*}}}*/ +// StateCache::Update - Compute the various static display things /*{{{*/ +// --------------------------------------------------------------------- +/* This is called whenever the Candidate version changes. */ +void pkgDepCache::StateCache::Update(PkgIterator Pkg,pkgCache &Cache) +{ + // Some info + VerIterator Ver = CandidateVerIter(Cache); + + // Use a null string or the version string + if (Ver.end() == true) + CandVersion = ""; + else + CandVersion = Ver.VerStr(); + + // Find the current version + CurVersion = ""; + if (Pkg->CurrentVer != 0) + CurVersion = Pkg.CurrentVer().VerStr(); + + // Figure out if its up or down or equal + Status = Ver.CompareVer(Pkg.CurrentVer()); + if (Pkg->CurrentVer == 0 || Pkg->VersionList == 0 || CandidateVer == 0) + Status = 2; +} + /*}}}*/ +// StateCache::StripEpoch - Remove the epoch specifier from the version /*{{{*/ +// --------------------------------------------------------------------- +/* */ +const char *pkgDepCache::StateCache::StripEpoch(const char *Ver) +{ + if (Ver == 0) + return 0; + + // Strip any epoch + char const * const I = strchr(Ver, ':'); + if (I == nullptr) + return Ver; + return I + 1; +} + /*}}}*/ +// Policy::GetCandidateVer - Returns the Candidate install version /*{{{*/ +// --------------------------------------------------------------------- +/* The default just returns the highest available version that is not + a source and automatic. */ +pkgCache::VerIterator pkgDepCache::Policy::GetCandidateVer(PkgIterator const &Pkg) +{ + /* Not source/not automatic versions cannot be a candidate version + unless they are already installed */ + VerIterator Last; + + for (VerIterator I = Pkg.VersionList(); I.end() == false; ++I) + { + if (Pkg.CurrentVer() == I) + return I; + + for (VerFileIterator J = I.FileList(); J.end() == false; ++J) + { + if (J.File().Flagged(Flag::NotSource)) + continue; + + /* Stash the highest version of a not-automatic source, we use it + if there is nothing better */ + if (J.File().Flagged(Flag::NotAutomatic) || + J.File().Flagged(Flag::ButAutomaticUpgrades)) + { + if (Last.end() == true) + Last = I; + continue; + } + + return I; + } + } + + return Last; +} + /*}}}*/ +// Policy::IsImportantDep - True if the dependency is important /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgDepCache::Policy::IsImportantDep(DepIterator const &Dep) const +{ + if(Dep.IsCritical()) + return true; + else if(Dep->Type == pkgCache::Dep::Recommends) + { + if (InstallRecommends) + return true; + // we support a special mode to only install-recommends for certain + // sections + // FIXME: this is a meant as a temporary solution until the + // recommends are cleaned up + const char *sec = Dep.ParentVer().Section(); + if (sec && ConfigValueInSubTree("APT::Install-Recommends-Sections", sec)) + return true; + } + else if(Dep->Type == pkgCache::Dep::Suggests) + return InstallSuggests; + + return false; +} + /*}}}*/ +// Policy::GetPriority - Get the priority of the package pin /*{{{*/ +APT_PURE signed short pkgDepCache::Policy::GetPriority(pkgCache::PkgIterator const &/*Pkg*/) +{ return 0; } +APT_PURE signed short pkgDepCache::Policy::GetPriority(pkgCache::VerIterator const &/*Ver*/, bool /*ConsiderFiles*/) +{ return 0; } +APT_PURE signed short pkgDepCache::Policy::GetPriority(pkgCache::PkgFileIterator const &/*File*/) +{ return 0; } + /*}}}*/ +pkgDepCache::InRootSetFunc *pkgDepCache::GetRootSetFunc() /*{{{*/ +{ + DefaultRootSetFunc *f = new DefaultRootSetFunc; + if(f->wasConstructedSuccessfully()) + return f; + else + { + delete f; + return NULL; + } +} + /*}}}*/ +bool pkgDepCache::MarkFollowsRecommends() +{ + return _config->FindB("APT::AutoRemove::RecommendsImportant", true); +} + +bool pkgDepCache::MarkFollowsSuggests() +{ + return _config->FindB("APT::AutoRemove::SuggestsImportant", true); +} + +// pkgDepCache::MarkRequired - the main mark algorithm /*{{{*/ +static bool IsPkgInBoringState(pkgCache::PkgIterator const &Pkg, pkgDepCache::StateCache const * const PkgState) +{ + if (Pkg->CurrentVer == 0) + { + if (PkgState[Pkg->ID].Keep()) + return true; + } + else + { + if (PkgState[Pkg->ID].Delete()) + return true; + } + return false; +} +bool pkgDepCache::MarkRequired(InRootSetFunc &userFunc) +{ + if (_config->Find("APT::Solver", "internal") != "internal") + return true; + + bool const debug_autoremove = _config->FindB("Debug::pkgAutoRemove",false); + + // init the states + auto const PackagesCount = Head().PackageCount; + for(auto i = decltype(PackagesCount){0}; i < PackagesCount; ++i) + { + PkgState[i].Marked = false; + PkgState[i].Garbage = false; + } + if (debug_autoremove) + for(PkgIterator p = PkgBegin(); !p.end(); ++p) + if(PkgState[p->ID].Flags & Flag::Auto) + std::clog << "AutoDep: " << p.FullName() << std::endl; + + bool const follow_recommends = MarkFollowsRecommends(); + bool const follow_suggests = MarkFollowsSuggests(); + + // do the mark part, this is the core bit of the algorithm + for (PkgIterator P = PkgBegin(); !P.end(); ++P) + { + if (PkgState[P->ID].Marked || IsPkgInBoringState(P, PkgState)) + continue; + + const char *reason = nullptr; + + if ((PkgState[P->ID].Flags & Flag::Auto) == 0) + reason = "Manual-Installed"; + else if (P->Flags & Flag::Essential) + reason = "Essential"; + else if (P->Flags & Flag::Important) + reason = "Important"; + else if (P->CurrentVer != 0 && P.CurrentVer()->Priority == pkgCache::State::Required) + reason = "Required"; + else if (userFunc.InRootSet(P)) + reason = "Blacklisted [APT::NeverAutoRemove]"; + else if (IsModeChangeOk(ModeGarbage, P, 0, false) == false) + reason = "Hold"; + else + continue; + + if (PkgState[P->ID].Install()) + MarkPackage(P, PkgState[P->ID].InstVerIter(*this), + follow_recommends, follow_suggests, reason); + else + MarkPackage(P, P.CurrentVer(), + follow_recommends, follow_suggests, reason); + } + + return true; +} + /*}}}*/ +// MarkPackage - mark a single package in Mark-and-Sweep /*{{{*/ +void pkgDepCache::MarkPackage(const pkgCache::PkgIterator &Pkg, + const pkgCache::VerIterator &Ver, + bool const &follow_recommends, + bool const &follow_suggests, + const char *reason) +{ + { + pkgDepCache::StateCache &state = PkgState[Pkg->ID]; + // if we are marked already we are done + if(state.Marked || unlikely(Ver.end())) + return; + state.Marked=true; + } + + if (IsPkgInBoringState(Pkg, PkgState)) + return; + + bool const debug_autoremove = _config->FindB("Debug::pkgAutoRemove", false); + if(debug_autoremove) + std::clog << "Marking: " << Pkg.FullName() << " " << Ver.VerStr() + << " (" << reason << ")" << std::endl; + + for (auto D = Ver.DependsList(); D.end() == false; ++D) + { + auto const T = D.TargetPkg(); + if (PkgState[T->ID].Marked) + continue; + + if (D->Type != Dep::Depends && + D->Type != Dep::PreDepends && + (follow_recommends == false || D->Type != Dep::Recommends) && + (follow_suggests == false || D->Type != Dep::Suggests)) + continue; + + // handle the virtual part first + APT::VersionVector providers; + for(auto Prv = T.ProvidesList(); Prv.end() == false; ++Prv) + { + auto PP = Prv.OwnerPkg(); + if (IsPkgInBoringState(PP, PkgState)) + continue; + + // we want to ignore provides from uninteresting versions + auto const PV = (PkgState[PP->ID].Install()) ? + PkgState[PP->ID].InstVerIter(*this) : PP.CurrentVer(); + if (unlikely(PV.end()) || PV != Prv.OwnerVer() || D.IsSatisfied(Prv) == false) + continue; + + providers.emplace_back(PV); + } + if (providers.empty() == false) + { + // sort providers by source version so that only the latest versioned + // binary package of a source package is marked instead of all + std::sort(providers.begin(), providers.end(), + [](pkgCache::VerIterator const &A, pkgCache::VerIterator const &B) { + auto const nameret = strcmp(A.SourcePkgName(), B.SourcePkgName()); + if (nameret != 0) + return nameret < 0; + auto const verret = A.Cache()->VS->CmpVersion(A.SourceVerStr(), B.SourceVerStr()); + if (verret != 0) + return verret > 0; + return strcmp(A.ParentPkg().Name(), B.ParentPkg().Name()) < 0; + }); + auto const prvsize = providers.size(); + providers.erase(std::unique(providers.begin(), providers.end(), + [](pkgCache::VerIterator const &A, pkgCache::VerIterator const &B) { + return strcmp(A.SourcePkgName(), B.SourcePkgName()) == 0 && + strcmp(A.SourceVerStr(), B.SourceVerStr()) != 0; + }), providers.end()); + for (auto && PV: providers) + { + auto const PP = PV.ParentPkg(); + if (debug_autoremove) + std::clog << "Following dep: " << APT::PrettyDep(this, D) + << ", provided by " << PP.FullName() << " " << PV.VerStr() + << " (" << providers.size() << "/" << prvsize << ")"<< std::endl; + MarkPackage(PP, PV, follow_recommends, follow_suggests, "Provider"); + } + } + + // now deal with the real part of the package + if (IsPkgInBoringState(T, PkgState)) + continue; + + auto const TV = (PkgState[T->ID].Install()) ? + PkgState[T->ID].InstVerIter(*this) : T.CurrentVer(); + if (unlikely(TV.end()) || D.IsSatisfied(TV) == false) + continue; + + if (debug_autoremove) + std::clog << "Following dep: " << APT::PrettyDep(this, D) << std::endl; + MarkPackage(T, TV, follow_recommends, follow_suggests, "Dependency"); + } +} + /*}}}*/ +bool pkgDepCache::Sweep() /*{{{*/ +{ + bool debug_autoremove = _config->FindB("Debug::pkgAutoRemove",false); + + // do the sweep + for(PkgIterator p=PkgBegin(); !p.end(); ++p) + { + StateCache &state=PkgState[p->ID]; + + // skip required packages + if (!p.CurrentVer().end() && + (p.CurrentVer()->Priority == pkgCache::State::Required)) + continue; + + // if it is not marked and it is installed, it's garbage + if(!state.Marked && (!p.CurrentVer().end() || state.Install())) + { + state.Garbage=true; + if(debug_autoremove) + std::clog << "Garbage: " << p.FullName() << std::endl; + } + } + + return true; +} + /*}}}*/ +// DepCache::MarkAndSweep /*{{{*/ +bool pkgDepCache::MarkAndSweep(InRootSetFunc &rootFunc) +{ + return MarkRequired(rootFunc) && Sweep(); +} +bool pkgDepCache::MarkAndSweep() +{ + std::unique_ptr<InRootSetFunc> f(GetRootSetFunc()); + if(f.get() != NULL) + return MarkAndSweep(*f.get()); + else + return false; +} + /*}}}*/ diff --git a/apt-pkg/depcache.h b/apt-pkg/depcache.h new file mode 100644 index 0000000..724bf56 --- /dev/null +++ b/apt-pkg/depcache.h @@ -0,0 +1,528 @@ +// -*- mode: c++; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + DepCache - Dependency Extension data for the cache + + This class stores the cache data and a set of extension structures for + monitoring the current state of all the packages. It also generates and + caches the 'install' state of many things. This refers to the state of the + package after an install has been run. + + The StateCache::State field can be -1,0,1,2 which is <,=,>,no current. + StateCache::Mode is which of the 3 fields is active. + + This structure is important to support the readonly status of the cache + file. When the data is saved the cache will be refereshed from our + internal rep and written to disk. Then the actual persistent data + files will be put on the disk. + + Each dependency is compared against 3 target versions to produce to + 3 dependency results. + Now - Compared using the Currently install version + Install - Compared using the install version (final state) + CVer - (Candidate Version) Compared using the Candidate Version + The candidate and now results are used to decide whether a package + should be automatically installed or if it should be left alone. + + Remember, the Candidate Version is selected based on the distribution + settings for the Package. The Install Version is selected based on the + state (Delete, Keep, Install) field and can be either the Current Version + or the Candidate version. + + The Candidate version is what is shown the 'Install Version' field. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_DEPCACHE_H +#define PKGLIB_DEPCACHE_H + +#include <apt-pkg/configuration.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#include <stddef.h> + +#include <list> +#include <memory> +#include <string> +#include <utility> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/error.h> +#include <apt-pkg/progress.h> +#endif +#ifndef APT_10_CLEANER_HEADERS +#include <set> +#include <vector> +#endif + +class OpProgress; +class pkgVersioningSystem; + +class pkgDepCache : protected pkgCache::Namespace +{ + public: + + /** \brief An arbitrary predicate on packages. */ + class InRootSetFunc + { + public: + virtual bool InRootSet(const pkgCache::PkgIterator &/*pkg*/) {return false;}; + virtual ~InRootSetFunc() {}; + }; + + private: + /** \brief Mark a single package and all its unmarked important + * dependencies during mark-and-sweep. + * + * Recursively invokes itself to mark all dependencies of the + * package. + * + * \param pkg The package to mark. + * + * \param ver The version of the package that is to be marked. + * + * \param follow_recommends If \b true, recommendations of the + * package will be recursively marked. + * + * \param follow_suggests If \b true, suggestions of the package + * will be recursively marked. + * + * \param reason The reason why the package is being marked. + * (Used in logging when Debug::pkgAutoRemove is set.) + */ + APT_HIDDEN void MarkPackage(const pkgCache::PkgIterator &pkg, + const pkgCache::VerIterator &ver, + bool const &follow_recommends, + bool const &follow_suggests, + const char *reason); + + /** \brief Update the Marked field of all packages. + * + * Each package's StateCache::Marked field will be set to \b true + * if and only if it can be reached from the root set. By + * default, the root set consists of the set of manually installed + * or essential packages, but it can be extended using the + * parameter #rootFunc. + * + * \param rootFunc A callback that can be used to add extra + * packages to the root set. + * + * \return \b false if an error occurred. + */ + bool MarkRequired(InRootSetFunc &rootFunc); + + /** \brief Set the StateCache::Garbage flag on all packages that + * should be removed. + * + * Packages that were not marked by the last call to #MarkRequired + * are tested to see whether they are actually garbage. If so, + * they are marked as such. + * + * \return \b false if an error occurred. + */ + bool Sweep(); + + public: + + // These flags are used in DepState + enum DepFlags {DepNow = (1 << 0),DepInstall = (1 << 1),DepCVer = (1 << 2), + DepGNow = (1 << 3),DepGInstall = (1 << 4),DepGCVer = (1 << 5)}; + + // These flags are used in StateCache::DepState + enum DepStateFlags {DepNowPolicy = (1 << 0), DepNowMin = (1 << 1), + DepInstPolicy = (1 << 2), DepInstMin = (1 << 3), + DepCandPolicy = (1 << 4), DepCandMin = (1 << 5)}; + + // These flags are used in StateCache::iFlags + enum InternalFlags {AutoKept = (1 << 0), Purge = (1 << 1), ReInstall = (1 << 2), Protected = (1 << 3)}; + + enum VersionTypes {NowVersion, InstallVersion, CandidateVersion}; + enum ModeList {ModeDelete = 0, ModeKeep = 1, ModeInstall = 2, ModeGarbage = 3}; + + /** \brief Represents an active action group. + * + * An action group is a group of actions that are currently being + * performed. While an active group is active, certain routine + * clean-up actions that would normally be performed after every + * cache operation are delayed until the action group is + * completed. This is necessary primarily to avoid inefficiencies + * when modifying a large number of packages at once. + * + * This class represents an active action group. Creating an + * instance will create an action group; destroying one will + * destroy the corresponding action group. + * + * The following operations are suppressed by this class: + * + * - Keeping the Marked and Garbage flags up to date. + * + * \note This can be used in the future to easily accumulate + * atomic actions for undo or to display "what apt did anyway"; + * e.g., change the counter of how many action groups are active + * to a std::set of pointers to them and use those to store + * information about what happened in a group in the group. + */ + class ActionGroup + { + void * const d; + pkgDepCache &cache; + + bool released; + + /** Action groups are noncopyable. */ + APT_HIDDEN ActionGroup(const ActionGroup &other); + public: + /** \brief Create a new ActionGroup. + * + * \param cache The cache that this ActionGroup should + * manipulate. + * + * As long as this object exists, no automatic cleanup + * operations will be undertaken. + */ + explicit ActionGroup(pkgDepCache &cache); + + /** \brief Clean up the action group before it is destroyed. + * + * If it is destroyed later, no second cleanup will be run. + */ + void release(); + + /** \brief Destroy the action group. + * + * If this is the last action group, the automatic cache + * cleanup operations will be undertaken. + */ + virtual ~ActionGroup(); + }; + + /** \brief Returns \b true for packages matching a regular + * expression in APT::NeverAutoRemove. + */ + class DefaultRootSetFunc : public InRootSetFunc, public Configuration::MatchAgainstConfig + { + public: + DefaultRootSetFunc() : Configuration::MatchAgainstConfig("APT::NeverAutoRemove") {}; + virtual ~DefaultRootSetFunc() {}; + + bool InRootSet(const pkgCache::PkgIterator &pkg) APT_OVERRIDE { return pkg.end() == false && Match(pkg.Name()); }; + }; + + struct StateCache + { + // text versions of the two version fields + const char *CandVersion; + const char *CurVersion; + + // Pointer to the candidate install version. + Version *CandidateVer; + + // Pointer to the install version. + Version *InstallVer; + + // Copy of Package::Flags + unsigned short Flags; + unsigned short iFlags; // Internal flags + + /** \brief \b true if this package can be reached from the root set. */ + bool Marked; + + /** \brief \b true if this package is unused and should be removed. + * + * This differs from !#Marked, because it is possible that some + * unreachable packages will be protected from becoming + * garbage. + */ + bool Garbage; + + // Various tree indicators + signed char Status; // -1,0,1,2 + unsigned char Mode; // ModeList + unsigned char DepState; // DepState Flags + + // Update of candidate version + APT_DEPRECATED_MSG("Use the method of the same name in contrib/strutl.h instead if you must") const char *StripEpoch(const char *Ver) APT_PURE; + void Update(PkgIterator Pkg,pkgCache &Cache); + + // Various test members for the current status of the package + inline bool NewInstall() const {return Status == 2 && Mode == ModeInstall;}; + inline bool Delete() const {return Mode == ModeDelete;}; + inline bool Purge() const {return Delete() == true && (iFlags & pkgDepCache::Purge) == pkgDepCache::Purge; }; + inline bool Keep() const {return Mode == ModeKeep;}; + inline bool Protect() const {return (iFlags & Protected) == Protected;}; + inline bool Upgrade() const {return Status > 0 && Mode == ModeInstall;}; + inline bool Upgradable() const {return Status >= 1 && CandidateVer != NULL;}; + inline bool Downgrade() const {return Status < 0 && Mode == ModeInstall;}; + inline bool Held() const {return Status != 0 && Keep();}; + inline bool NowBroken() const {return (DepState & DepNowMin) != DepNowMin;}; + inline bool NowPolicyBroken() const {return (DepState & DepNowPolicy) != DepNowPolicy;}; + inline bool InstBroken() const {return (DepState & DepInstMin) != DepInstMin;}; + inline bool InstPolicyBroken() const {return (DepState & DepInstPolicy) != DepInstPolicy;}; + inline bool Install() const {return Mode == ModeInstall;}; + inline bool ReInstall() const {return Delete() == false && (iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall;}; + inline VerIterator InstVerIter(pkgCache &Cache) + {return VerIterator(Cache,InstallVer);}; + inline VerIterator CandidateVerIter(pkgCache &Cache) + {return VerIterator(Cache,CandidateVer);}; + }; + + // Helper functions + void BuildGroupOrs(VerIterator const &V); + void UpdateVerState(PkgIterator const &Pkg); + + // User Policy control + class Policy + { + public: + Policy() { + InstallRecommends = _config->FindB("APT::Install-Recommends", false); + InstallSuggests = _config->FindB("APT::Install-Suggests", false); + } + + virtual VerIterator GetCandidateVer(PkgIterator const &Pkg); + virtual bool IsImportantDep(DepIterator const &Dep) const; + virtual signed short GetPriority(PkgIterator const &Pkg); + virtual signed short GetPriority(VerIterator const &Ver, bool ConsiderFiles=true); + virtual signed short GetPriority(PkgFileIterator const &File); + + virtual ~Policy() {}; + + private: + bool InstallRecommends; + bool InstallSuggests; + }; + + private: + /** The number of open "action groups"; certain post-action + * operations are suppressed if this number is > 0. + */ + int group_level; + + friend class ActionGroup; + + protected: + + // State information + pkgCache *Cache; + StateCache *PkgState; + unsigned char *DepState; + + /** Stores the space changes after installation */ + signed long long iUsrSize; + /** Stores how much we need to download to get the packages */ + unsigned long long iDownloadSize; + unsigned long iInstCount; + unsigned long iDelCount; + unsigned long iKeepCount; + unsigned long iBrokenCount; + unsigned long iPolicyBrokenCount; + unsigned long iBadCount; + + bool DebugMarker; + bool DebugAutoInstall; + + Policy *delLocalPolicy; // For memory clean up.. + Policy *LocalPolicy; + + // Check for a matching provides + bool CheckDep(DepIterator const &Dep,int const Type,PkgIterator &Res); + inline bool CheckDep(DepIterator const &Dep,int const Type) + { + PkgIterator Res(*this,0); + return CheckDep(Dep,Type,Res); + } + + // Computes state information for deps and versions (w/o storing) + unsigned char DependencyState(DepIterator const &D); + unsigned char VersionState(DepIterator D,unsigned char const Check, + unsigned char const SetMin, + unsigned char const SetPolicy) const; + + // Recalculates various portions of the cache, call after changing something + void Update(DepIterator Dep); // Mostly internal + void Update(PkgIterator const &P); + + // Count manipulators + void AddSizes(const PkgIterator &Pkg, bool const Invert = false); + inline void RemoveSizes(const PkgIterator &Pkg) {AddSizes(Pkg, true);}; + void AddStates(const PkgIterator &Pkg, bool const Invert = false); + inline void RemoveStates(const PkgIterator &Pkg) {AddStates(Pkg,true);}; + + public: + + // Legacy.. We look like a pkgCache + inline operator pkgCache &() {return *Cache;}; + inline Header &Head() {return *Cache->HeaderP;}; + inline GrpIterator GrpBegin() {return Cache->GrpBegin();}; + inline PkgIterator PkgBegin() {return Cache->PkgBegin();}; + inline GrpIterator FindGrp(std::string const &Name) {return Cache->FindGrp(Name);}; + inline PkgIterator FindPkg(std::string const &Name) {return Cache->FindPkg(Name);}; + inline PkgIterator FindPkg(std::string const &Name, std::string const &Arch) {return Cache->FindPkg(Name, Arch);}; + + inline pkgCache &GetCache() {return *Cache;}; + inline pkgVersioningSystem &VS() {return *Cache->VS;}; + + // Policy implementation + APT_DEPRECATED_MSG("Confusingly named method which returns the candidate as chosen by policy (NOT as chosen via .SetCandidateVersion!). You probably want to use .GetCandidateVersion instead.") inline VerIterator GetCandidateVer(PkgIterator const &Pkg) {return /* GetCandidateVersion(Pkg); but for API compat: */ LocalPolicy->GetCandidateVer(Pkg);}; + + inline bool IsImportantDep(DepIterator Dep) const {return LocalPolicy->IsImportantDep(Dep);}; + inline Policy &GetPolicy() {return *LocalPolicy;}; + + // Accessors + inline StateCache &operator [](PkgIterator const &I) {return PkgState[I->ID];}; + inline StateCache &operator [](PkgIterator const &I) const {return PkgState[I->ID];}; + inline unsigned char &operator [](DepIterator const &I) {return DepState[I->ID];}; + + /** \return A function identifying packages in the root set other + * than manually installed packages and essential packages, or \b + * NULL if an error occurs. + * + * \todo Is this the best place for this function? Perhaps the + * settings for mark-and-sweep should be stored in a single + * external class? + */ + virtual InRootSetFunc *GetRootSetFunc(); + + /** \return \b true if the garbage collector should follow recommendations. + */ + virtual bool MarkFollowsRecommends(); + + /** \return \b true if the garbage collector should follow suggestions. + */ + virtual bool MarkFollowsSuggests(); + + /** \brief Update the Marked and Garbage fields of all packages. + * + * This routine is implicitly invoked after all state manipulators + * and when an ActionGroup is destroyed. It invokes the private + * MarkRequired() and Sweep() to do its dirty work. + * + * \param rootFunc A predicate that returns \b true for packages + * that should be added to the root set. + */ + bool MarkAndSweep(InRootSetFunc &rootFunc); + bool MarkAndSweep(); + + /** \name State Manipulators + */ + // @{ + bool MarkKeep(PkgIterator const &Pkg, bool Soft = false, + bool FromUser = true, unsigned long Depth = 0); + bool MarkDelete(PkgIterator const &Pkg, bool MarkPurge = false, + unsigned long Depth = 0, bool FromUser = true); + bool MarkInstall(PkgIterator const &Pkg,bool AutoInst = true, + unsigned long Depth = 0, bool FromUser = true, + bool ForceImportantDeps = false); + void MarkProtected(PkgIterator const &Pkg) { PkgState[Pkg->ID].iFlags |= Protected; }; + + void SetReInstall(PkgIterator const &Pkg,bool To); + + /** @return 'the' candidate version of a package + * + * The version returned is the version previously set explicitly via + * SetCandidate* methods like #SetCandidateVersion or if there wasn't one + * set the version as chosen via #Policy. + * + * @param Pkg is the package to return the candidate for + */ + pkgCache::VerIterator GetCandidateVersion(pkgCache::PkgIterator const &Pkg); + void SetCandidateVersion(VerIterator TargetVer); + bool SetCandidateRelease(pkgCache::VerIterator TargetVer, + std::string const &TargetRel); + /** Set the candidate version for dependencies too if needed. + * + * Sets not only the candidate version as SetCandidateVersion does, + * but walks also down the dependency tree and checks if it is required + * to set the candidate of the dependency to a version from the given + * release, too. + * + * \param TargetVer new candidate version of the package + * \param TargetRel try to switch to this release if needed + * \param[out] Changed a list of pairs consisting of the \b old + * version of the changed package and the version which + * required the switch of this dependency + * \return \b true if the switch was successful, \b false otherwise + */ + bool SetCandidateRelease(pkgCache::VerIterator TargetVer, + std::string const &TargetRel, + std::list<std::pair<pkgCache::VerIterator, pkgCache::VerIterator> > &Changed); + + /** Set the "is automatically installed" flag of Pkg. */ + void MarkAuto(const PkgIterator &Pkg, bool Auto); + // @} + + /** \return \b true if it's OK for MarkInstall to install + * the given package. + * + * The default implementation simply calls all IsInstallOk* + * method mentioned below. + * + * Overriding implementations should use the hold-state-flag to + * cache results from previous checks of this package - if possible. + * + * The parameters are the same as in the calling MarkInstall: + * \param Pkg the package that MarkInstall wants to install. + * \param AutoInst install this and all its dependencies + * \param Depth recursive deep of this Marker call + * \param FromUser was the install requested by the user? + */ + virtual bool IsInstallOk(const PkgIterator &Pkg,bool AutoInst = true, + unsigned long Depth = 0, bool FromUser = true); + + /** \return \b true if it's OK for MarkDelete to remove + * the given package. + * + * The default implementation simply calls all IsDeleteOk* + * method mentioned below, see also #IsInstallOk. + * + * The parameters are the same as in the calling MarkDelete: + * \param Pkg the package that MarkDelete wants to remove. + * \param MarkPurge should we purge instead of "only" remove? + * \param Depth recursive deep of this Marker call + * \param FromUser was the remove requested by the user? + */ + virtual bool IsDeleteOk(const PkgIterator &Pkg,bool MarkPurge = false, + unsigned long Depth = 0, bool FromUser = true); + + // read persistent states + bool readStateFile(OpProgress * const prog); + bool writeStateFile(OpProgress * const prog, bool const InstalledOnly=true); + + // Size queries + inline signed long long UsrSize() {return iUsrSize;}; + inline unsigned long long DebSize() {return iDownloadSize;}; + inline unsigned long DelCount() {return iDelCount;}; + inline unsigned long KeepCount() {return iKeepCount;}; + inline unsigned long InstCount() {return iInstCount;}; + inline unsigned long BrokenCount() {return iBrokenCount;}; + inline unsigned long PolicyBrokenCount() {return iPolicyBrokenCount;}; + inline unsigned long BadCount() {return iBadCount;}; + + bool Init(OpProgress * const Prog); + // Generate all state information + void Update(OpProgress * const Prog = 0); + + pkgDepCache(pkgCache * const Cache,Policy * const Plcy = 0); + virtual ~pkgDepCache(); + + protected: + // methods call by IsInstallOk + bool IsInstallOkMultiArchSameVersionSynced(PkgIterator const &Pkg, + bool const AutoInst, unsigned long const Depth, bool const FromUser); + bool IsInstallOkDependenciesSatisfiableByCandidates(PkgIterator const &Pkg, + bool const AutoInst, unsigned long const Depth, bool const FromUser); + + // methods call by IsDeleteOk + bool IsDeleteOkProtectInstallRequests(PkgIterator const &Pkg, + bool const rPurge, unsigned long const Depth, bool const FromUser); + + private: + void * const d; + + APT_HIDDEN bool IsModeChangeOk(ModeList const mode, PkgIterator const &Pkg, + unsigned long const Depth, bool const FromUser); +}; + +#endif diff --git a/apt-pkg/edsp.cc b/apt-pkg/edsp.cc new file mode 100644 index 0000000..2e39be3 --- /dev/null +++ b/apt-pkg/edsp.cc @@ -0,0 +1,1505 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + Set of methods to help writing and reading everything needed for EDSP + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/algorithms.h> +#include <apt-pkg/cacheset.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/edsp.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/packagemanager.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgsystem.h> +#include <apt-pkg/prettyprinters.h> +#include <apt-pkg/progress.h> +#include <apt-pkg/string_view.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile.h> + +#include <ctype.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <array> +#include <limits> +#include <string> + +#include <apti18n.h> + /*}}}*/ + +using std::string; + +// we could use pkgCache::DepType and ::Priority, but these would be localized strings… +constexpr char const * const PrioMap[] = { + nullptr, "important", "required", "standard", + "optional", "extra" +}; +constexpr char const * const DepMap[] = { + nullptr, "Depends", "Pre-Depends", "Suggests", + "Recommends" , "Conflicts", "Replaces", + "Obsoletes", "Breaks", "Enhances" +}; + +// WriteOkay - varaidic helper to easily Write to a FileFd /*{{{*/ +static bool WriteOkay_fn(FileFd &) { return true; } +template<typename... Tail> static bool WriteOkay_fn(FileFd &output, APT::StringView data, Tail... more_data) +{ + return likely(output.Write(data.data(), data.length()) && WriteOkay_fn(output, more_data...)); +} +template<typename... Tail> static bool WriteOkay_fn(FileFd &output, unsigned int data, Tail... more_data) +{ + std::string number; + strprintf(number, "%d", data); + return likely(output.Write(number.data(), number.length()) && WriteOkay_fn(output, more_data...)); +} +template<typename... Data> static bool WriteOkay(bool &Okay, FileFd &output, Data&&... data) +{ + Okay = likely(Okay && WriteOkay_fn(output, std::forward<Data>(data)...)); + return Okay; +} +template<typename... Data> static bool WriteOkay(FileFd &output, Data&&... data) +{ + bool Okay = likely(output.Failed() == false); + return WriteOkay(Okay, output, std::forward<Data>(data)...); +} + /*}}}*/ +// WriteScenarioVersion /*{{{*/ +static void WriteScenarioVersion(pkgDepCache &Cache, FILE* output, pkgCache::PkgIterator const &Pkg, + pkgCache::VerIterator const &Ver) +{ + fprintf(output, "Package: %s\n", Pkg.Name()); + fprintf(output, "Source: %s\n", Ver.SourcePkgName()); + fprintf(output, "Architecture: %s\n", Ver.Arch()); + fprintf(output, "Version: %s\n", Ver.VerStr()); + fprintf(output, "Source-Version: %s\n", Ver.SourceVerStr()); + if (Pkg.CurrentVer() == Ver) + fprintf(output, "Installed: yes\n"); + if (Pkg->SelectedState == pkgCache::State::Hold || + (Cache[Pkg].Keep() == true && Cache[Pkg].Protect() == true)) + fprintf(output, "Hold: yes\n"); + fprintf(output, "APT-ID: %d\n", Ver->ID); + if (PrioMap[Ver->Priority] != nullptr) + fprintf(output, "Priority: %s\n", PrioMap[Ver->Priority]); + if ((Pkg->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential) + fprintf(output, "Essential: yes\n"); + if (Ver->Section != 0) + fprintf(output, "Section: %s\n", Ver.Section()); + if ((Ver->MultiArch & pkgCache::Version::Allowed) == pkgCache::Version::Allowed) + fprintf(output, "Multi-Arch: allowed\n"); + else if ((Ver->MultiArch & pkgCache::Version::Foreign) == pkgCache::Version::Foreign) + fprintf(output, "Multi-Arch: foreign\n"); + else if ((Ver->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same) + fprintf(output, "Multi-Arch: same\n"); + std::set<string> Releases; + for (pkgCache::VerFileIterator I = Ver.FileList(); I.end() == false; ++I) { + pkgCache::PkgFileIterator File = I.File(); + if (File.Flagged(pkgCache::Flag::NotSource) == false) { + string Release = File.RelStr(); + if (!Release.empty()) + Releases.insert(Release); + } + } + if (!Releases.empty()) { + fprintf(output, "APT-Release:\n"); + for (std::set<string>::iterator R = Releases.begin(); R != Releases.end(); ++R) + fprintf(output, " %s\n", R->c_str()); + } + fprintf(output, "APT-Pin: %d\n", Cache.GetPolicy().GetPriority(Ver)); + if (Cache.GetCandidateVersion(Pkg) == Ver) + fprintf(output, "APT-Candidate: yes\n"); + if ((Cache[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) + fprintf(output, "APT-Automatic: yes\n"); +} +static bool WriteScenarioVersion(FileFd &output, pkgCache::PkgIterator const &Pkg, + pkgCache::VerIterator const &Ver) +{ + bool Okay = WriteOkay(output, "Package: ", Pkg.Name(), + "\nArchitecture: ", Ver.Arch(), + "\nVersion: ", Ver.VerStr()); + WriteOkay(Okay, output, "\nAPT-ID: ", Ver->ID); + if ((Pkg->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential) + WriteOkay(Okay, output, "\nEssential: yes"); + if ((Ver->MultiArch & pkgCache::Version::Allowed) == pkgCache::Version::Allowed) + WriteOkay(Okay, output, "\nMulti-Arch: allowed"); + else if ((Ver->MultiArch & pkgCache::Version::Foreign) == pkgCache::Version::Foreign) + WriteOkay(Okay, output, "\nMulti-Arch: foreign"); + else if ((Ver->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same) + WriteOkay(Okay, output, "\nMulti-Arch: same"); + return Okay; +} + /*}}}*/ +// WriteScenarioDependency /*{{{*/ +static void WriteScenarioDependency( FILE* output, pkgCache::VerIterator const &Ver) +{ + std::array<std::string, _count(DepMap)> dependencies; + bool orGroup = false; + for (pkgCache::DepIterator Dep = Ver.DependsList(); Dep.end() == false; ++Dep) + { + if (Dep.IsImplicit() == true) + continue; + if (orGroup == false) + dependencies[Dep->Type].append(", "); + dependencies[Dep->Type].append(Dep.TargetPkg().Name()); + if (Dep->Version != 0) + dependencies[Dep->Type].append(" (").append(pkgCache::CompTypeDeb(Dep->CompareOp)).append(" ").append(Dep.TargetVer()).append(")"); + if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or) + { + dependencies[Dep->Type].append(" | "); + orGroup = true; + } + else + orGroup = false; + } + for (size_t i = 1; i < dependencies.size(); ++i) + if (dependencies[i].empty() == false) + fprintf(output, "%s: %s\n", DepMap[i], dependencies[i].c_str()+2); + string provides; + for (pkgCache::PrvIterator Prv = Ver.ProvidesList(); Prv.end() == false; ++Prv) + { + if (Prv.IsMultiArchImplicit() == true) + continue; + if (provides.empty() == false) + provides.append(", "); + provides.append(Prv.Name()); + if (Prv->ProvideVersion != 0) + provides.append(" (= ").append(Prv.ProvideVersion()).append(")"); + } + if (provides.empty() == false) + fprintf(output, "Provides: %s\n", provides.c_str()); +} +static bool WriteScenarioDependency(FileFd &output, pkgCache::VerIterator const &Ver, bool const OnlyCritical) +{ + std::array<std::string, _count(DepMap)> dependencies; + bool orGroup = false; + for (pkgCache::DepIterator Dep = Ver.DependsList(); Dep.end() == false; ++Dep) + { + if (Dep.IsImplicit() == true) + continue; + if (OnlyCritical && Dep.IsCritical() == false) + continue; + if (orGroup == false && dependencies[Dep->Type].empty() == false) + dependencies[Dep->Type].append(", "); + dependencies[Dep->Type].append(Dep.TargetPkg().Name()); + if (Dep->Version != 0) + dependencies[Dep->Type].append(" (").append(pkgCache::CompTypeDeb(Dep->CompareOp)).append(" ").append(Dep.TargetVer()).append(")"); + if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or) + { + dependencies[Dep->Type].append(" | "); + orGroup = true; + } + else + orGroup = false; + } + bool Okay = output.Failed() == false; + for (size_t i = 1; i < dependencies.size(); ++i) + if (dependencies[i].empty() == false) + WriteOkay(Okay, output, "\n", DepMap[i], ": ", dependencies[i]); + string provides; + for (pkgCache::PrvIterator Prv = Ver.ProvidesList(); Prv.end() == false; ++Prv) + { + if (Prv.IsMultiArchImplicit() == true) + continue; + if (provides.empty() == false) + provides.append(", "); + provides.append(Prv.Name()); + if (Prv->ProvideVersion != 0) + provides.append(" (= ").append(Prv.ProvideVersion()).append(")"); + } + if (provides.empty() == false) + WriteOkay(Okay, output, "\nProvides: ", provides); + return WriteOkay(Okay, output, "\n"); +} + /*}}}*/ +// WriteScenarioLimitedDependency /*{{{*/ +static void WriteScenarioLimitedDependency(FILE* output, + pkgCache::VerIterator const &Ver, + APT::PackageSet const &pkgset) +{ + std::array<std::string, _count(DepMap)> dependencies; + bool orGroup = false; + for (pkgCache::DepIterator Dep = Ver.DependsList(); Dep.end() == false; ++Dep) + { + if (Dep.IsImplicit() == true) + continue; + if (orGroup == false) + { + if (pkgset.find(Dep.TargetPkg()) == pkgset.end()) + continue; + if (dependencies[Dep->Type].empty() == false) + dependencies[Dep->Type].append(", "); + } + else if (pkgset.find(Dep.TargetPkg()) == pkgset.end()) + { + if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or) + continue; + dependencies[Dep->Type].erase(dependencies[Dep->Type].end()-3, dependencies[Dep->Type].end()); + orGroup = false; + continue; + } + dependencies[Dep->Type].append(Dep.TargetPkg().Name()); + if (Dep->Version != 0) + dependencies[Dep->Type].append(" (").append(pkgCache::CompTypeDeb(Dep->CompareOp)).append(" ").append(Dep.TargetVer()).append(")"); + if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or) + { + dependencies[Dep->Type].append(" | "); + orGroup = true; + } + else + orGroup = false; + } + for (size_t i = 1; i < dependencies.size(); ++i) + if (dependencies[i].empty() == false) + fprintf(output, "%s: %s\n", DepMap[i], dependencies[i].c_str()); + string provides; + for (pkgCache::PrvIterator Prv = Ver.ProvidesList(); Prv.end() == false; ++Prv) + { + if (Prv.IsMultiArchImplicit() == true) + continue; + if (pkgset.find(Prv.ParentPkg()) == pkgset.end()) + continue; + if (provides.empty() == false) + provides.append(", "); + provides.append(Prv.Name()); + if (Prv->ProvideVersion != 0) + provides.append(" (= ").append(Prv.ProvideVersion()).append(")"); + } + if (provides.empty() == false) + fprintf(output, "Provides: %s\n", provides.c_str()); +} +static bool WriteScenarioLimitedDependency(FileFd &output, + pkgCache::VerIterator const &Ver, + std::vector<bool> const &pkgset, + bool const OnlyCritical) +{ + std::array<std::string, _count(DepMap)> dependencies; + bool orGroup = false; + for (pkgCache::DepIterator Dep = Ver.DependsList(); Dep.end() == false; ++Dep) + { + if (Dep.IsImplicit() == true) + continue; + if (OnlyCritical && Dep.IsCritical() == false) + continue; + if (orGroup == false) + { + if (pkgset[Dep.TargetPkg()->ID] == false) + continue; + if (dependencies[Dep->Type].empty() == false) + dependencies[Dep->Type].append(", "); + } + else if (pkgset[Dep.TargetPkg()->ID] == false) + { + if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or) + continue; + dependencies[Dep->Type].erase(dependencies[Dep->Type].end()-3, dependencies[Dep->Type].end()); + orGroup = false; + continue; + } + dependencies[Dep->Type].append(Dep.TargetPkg().Name()); + if (Dep->Version != 0) + dependencies[Dep->Type].append(" (").append(pkgCache::CompTypeDeb(Dep->CompareOp)).append(" ").append(Dep.TargetVer()).append(")"); + if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or) + { + dependencies[Dep->Type].append(" | "); + orGroup = true; + } + else + orGroup = false; + } + bool Okay = output.Failed() == false; + for (size_t i = 1; i < dependencies.size(); ++i) + if (dependencies[i].empty() == false) + WriteOkay(Okay, output, "\n", DepMap[i], ": ", dependencies[i]); + string provides; + for (pkgCache::PrvIterator Prv = Ver.ProvidesList(); Prv.end() == false; ++Prv) + { + if (Prv.IsMultiArchImplicit() == true) + continue; + if (pkgset[Prv.ParentPkg()->ID] == false) + continue; + if (provides.empty() == false) + provides.append(", "); + provides.append(Prv.Name()); + if (Prv->ProvideVersion != 0) + provides.append(" (= ").append(Prv.ProvideVersion()).append(")"); + } + if (provides.empty() == false) + WriteOkay(Okay, output, "\nProvides: ", provides); + return WriteOkay(Okay, output, "\n"); +} + /*}}}*/ +static bool SkipUnavailableVersions(pkgDepCache &Cache, pkgCache::PkgIterator const &Pkg, pkgCache::VerIterator const &Ver)/*{{{*/ +{ + /* versions which aren't current and aren't available in + any "online" source file are bad, expect if they are the chosen + candidate: The exception is for build-dep implementation as it creates + such pseudo (package) versions and removes them later on again. + We filter out versions at all so packages in 'rc' state only available + in dpkg/status aren't passed to solvers as they can't be installed. */ + if (Pkg->CurrentVer != 0) + return false; + if (Cache.GetCandidateVersion(Pkg) == Ver) + return false; + for (pkgCache::VerFileIterator I = Ver.FileList(); I.end() == false; ++I) + if (I.File().Flagged(pkgCache::Flag::NotSource) == false) + return false; + return true; +} + /*}}}*/ +static bool WriteScenarioEDSPVersion(pkgDepCache &Cache, FileFd &output, pkgCache::PkgIterator const &Pkg,/*{{{*/ + pkgCache::VerIterator const &Ver) +{ + bool Okay = WriteOkay(output, "\nSource: ", Ver.SourcePkgName(), + "\nSource-Version: ", Ver.SourceVerStr()); + if (PrioMap[Ver->Priority] != nullptr) + WriteOkay(Okay, output, "\nPriority: ", PrioMap[Ver->Priority]); + if (Ver->Section != 0) + WriteOkay(Okay, output, "\nSection: ", Ver.Section()); + if (Pkg.CurrentVer() == Ver) + WriteOkay(Okay, output, "\nInstalled: yes"); + if (Pkg->SelectedState == pkgCache::State::Hold || + (Cache[Pkg].Keep() == true && Cache[Pkg].Protect() == true)) + WriteOkay(Okay, output, "\nHold: yes"); + std::set<string> Releases; + for (pkgCache::VerFileIterator I = Ver.FileList(); I.end() == false; ++I) { + pkgCache::PkgFileIterator File = I.File(); + if (File.Flagged(pkgCache::Flag::NotSource) == false) { + string Release = File.RelStr(); + if (!Release.empty()) + Releases.insert(Release); + } + } + if (!Releases.empty()) { + WriteOkay(Okay, output, "\nAPT-Release:"); + for (std::set<string>::iterator R = Releases.begin(); R != Releases.end(); ++R) + WriteOkay(Okay, output, "\n ", *R); + } + WriteOkay(Okay, output, "\nAPT-Pin: ", Cache.GetPolicy().GetPriority(Ver)); + if (Cache.GetCandidateVersion(Pkg) == Ver) + WriteOkay(Okay, output, "\nAPT-Candidate: yes"); + if ((Cache[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) + WriteOkay(Okay, output, "\nAPT-Automatic: yes"); + return Okay; +} + /*}}}*/ +// EDSP::WriteScenario - to the given file descriptor /*{{{*/ +bool EDSP::WriteScenario(pkgDepCache &Cache, FILE* output, OpProgress *Progress) +{ + if (Progress != NULL) + Progress->SubProgress(Cache.Head().VersionCount, _("Send scenario to solver")); + decltype(Cache.Head().VersionCount) p = 0; + std::vector<std::string> archs = APT::Configuration::getArchitectures(); + for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg) + { + std::string const arch = Pkg.Arch(); + if (std::find(archs.begin(), archs.end(), arch) == archs.end()) + continue; + for (pkgCache::VerIterator Ver = Pkg.VersionList(); Ver.end() == false; ++Ver, ++p) + { + if (SkipUnavailableVersions(Cache, Pkg, Ver)) + continue; + WriteScenarioVersion(Cache, output, Pkg, Ver); + WriteScenarioDependency(output, Ver); + fprintf(output, "\n"); + if (Progress != NULL && p % 100 == 0) + Progress->Progress(p); + } + } + return true; +} +bool EDSP::WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress *Progress) +{ + if (Progress != NULL) + Progress->SubProgress(Cache.Head().VersionCount, _("Send scenario to solver")); + decltype(Cache.Head().VersionCount) p = 0; + bool Okay = output.Failed() == false; + std::vector<std::string> archs = APT::Configuration::getArchitectures(); + for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false && likely(Okay); ++Pkg) + { + std::string const arch = Pkg.Arch(); + if (std::find(archs.begin(), archs.end(), arch) == archs.end()) + continue; + for (pkgCache::VerIterator Ver = Pkg.VersionList(); Ver.end() == false && likely(Okay); ++Ver, ++p) + { + if (SkipUnavailableVersions(Cache, Pkg, Ver)) + continue; + Okay &= WriteScenarioVersion(output, Pkg, Ver); + Okay &= WriteScenarioEDSPVersion(Cache, output, Pkg, Ver); + Okay &= WriteScenarioDependency(output, Ver, false); + WriteOkay(Okay, output, "\n"); + if (Progress != NULL && p % 100 == 0) + Progress->Progress(p); + } + } + return Okay; +} + /*}}}*/ +// EDSP::WriteLimitedScenario - to the given file descriptor /*{{{*/ +bool EDSP::WriteLimitedScenario(pkgDepCache &Cache, FILE* output, + APT::PackageSet const &pkgset, + OpProgress *Progress) +{ + if (Progress != NULL) + Progress->SubProgress(Cache.Head().VersionCount, _("Send scenario to solver")); + decltype(Cache.Head().PackageCount) p = 0; + for (APT::PackageSet::const_iterator Pkg = pkgset.begin(); Pkg != pkgset.end(); ++Pkg, ++p) + for (pkgCache::VerIterator Ver = Pkg.VersionList(); Ver.end() == false; ++Ver) + { + if (SkipUnavailableVersions(Cache, Pkg, Ver)) + continue; + WriteScenarioVersion(Cache, output, Pkg, Ver); + WriteScenarioLimitedDependency(output, Ver, pkgset); + fprintf(output, "\n"); + if (Progress != NULL && p % 100 == 0) + Progress->Progress(p); + } + if (Progress != NULL) + Progress->Done(); + return true; +} +bool EDSP::WriteLimitedScenario(pkgDepCache &Cache, FileFd &output, + std::vector<bool> const &pkgset, + OpProgress *Progress) +{ + if (Progress != NULL) + Progress->SubProgress(Cache.Head().VersionCount, _("Send scenario to solver")); + decltype(Cache.Head().PackageCount) p = 0; + bool Okay = output.Failed() == false; + for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false && likely(Okay); ++Pkg, ++p) + { + if (pkgset[Pkg->ID] == false) + continue; + for (pkgCache::VerIterator Ver = Pkg.VersionList(); Ver.end() == false && likely(Okay); ++Ver) + { + if (SkipUnavailableVersions(Cache, Pkg, Ver)) + continue; + Okay &= WriteScenarioVersion(output, Pkg, Ver); + Okay &= WriteScenarioEDSPVersion(Cache, output, Pkg, Ver); + Okay &= WriteScenarioLimitedDependency(output, Ver, pkgset, false); + WriteOkay(Okay, output, "\n"); + if (Progress != NULL && p % 100 == 0) + Progress->Progress(p); + } + } + if (Progress != NULL) + Progress->Done(); + return Okay; +} + /*}}}*/ +// EDSP::WriteRequest - to the given file descriptor /*{{{*/ +bool EDSP::WriteRequest(pkgDepCache &Cache, FILE* output, bool const Upgrade, + bool const DistUpgrade, bool const AutoRemove, + OpProgress *Progress) +{ + if (Progress != NULL) + Progress->SubProgress(Cache.Head().PackageCount, _("Send request to solver")); + decltype(Cache.Head().PackageCount) p = 0; + string del, inst; + for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg, ++p) + { + if (Progress != NULL && p % 100 == 0) + Progress->Progress(p); + string* req; + pkgDepCache::StateCache &P = Cache[Pkg]; + if (P.Delete() == true) + req = &del; + else if (P.NewInstall() == true || P.Upgrade() == true || P.ReInstall() == true || + (P.Mode == pkgDepCache::ModeKeep && (P.iFlags & pkgDepCache::Protected) == pkgDepCache::Protected)) + req = &inst; + else + continue; + req->append(" ").append(Pkg.FullName()); + } + fprintf(output, "Request: EDSP 0.5\n"); + + const char *arch = _config->Find("APT::Architecture").c_str(); + std::vector<string> archs = APT::Configuration::getArchitectures(); + fprintf(output, "Architecture: %s\n", arch); + fprintf(output, "Architectures:"); + for (std::vector<string>::const_iterator a = archs.begin(); a != archs.end(); ++a) + fprintf(output, " %s", a->c_str()); + fprintf(output, "\n"); + + if (del.empty() == false) + fprintf(output, "Remove: %s\n", del.c_str()+1); + if (inst.empty() == false) + fprintf(output, "Install: %s\n", inst.c_str()+1); + if (Upgrade == true) + fprintf(output, "Upgrade: yes\n"); + if (DistUpgrade == true) + fprintf(output, "Dist-Upgrade: yes\n"); + if (AutoRemove == true) + fprintf(output, "Autoremove: yes\n"); + auto const solver = _config->Find("APT::Solver", "internal"); + fprintf(output, "Solver: %s\n", solver.c_str()); + auto const solverconf = std::string("APT::Solver::") + solver + "::"; + if (_config->FindB(solverconf + "Strict-Pinning", _config->FindB("APT::Solver::Strict-Pinning", true)) == false) + fprintf(output, "Strict-Pinning: no\n"); + auto const solverpref = _config->Find(solverconf + "Preferences", _config->Find("APT::Solver::Preferences", "")); + if (solverpref.empty() == false) + fprintf(output, "Preferences: %s\n", solverpref.c_str()); + fprintf(output, "\n"); + return true; +} +bool EDSP::WriteRequest(pkgDepCache &Cache, FileFd &output, + unsigned int const flags, + OpProgress *Progress) +{ + if (Progress != NULL) + Progress->SubProgress(Cache.Head().PackageCount, _("Send request to solver")); + decltype(Cache.Head().PackageCount) p = 0; + string del, inst; + for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg, ++p) + { + if (Progress != NULL && p % 100 == 0) + Progress->Progress(p); + string* req; + pkgDepCache::StateCache &P = Cache[Pkg]; + if (P.Delete() == true) + req = &del; + else if (P.NewInstall() == true || P.Upgrade() == true || P.ReInstall() == true || + (P.Mode == pkgDepCache::ModeKeep && (P.iFlags & pkgDepCache::Protected) == pkgDepCache::Protected)) + req = &inst; + else + continue; + req->append(" ").append(Pkg.FullName()); + } + bool Okay = WriteOkay(output, "Request: EDSP 0.5\n"); + + const char *arch = _config->Find("APT::Architecture").c_str(); + std::vector<string> archs = APT::Configuration::getArchitectures(); + WriteOkay(Okay, output, "Architecture: ", arch, "\n", + "Architectures:"); + for (std::vector<string>::const_iterator a = archs.begin(); a != archs.end(); ++a) + WriteOkay(Okay, output, " ", *a); + WriteOkay(Okay, output, "\n"); + + if (del.empty() == false) + WriteOkay(Okay, output, "Remove:", del, "\n"); + if (inst.empty() == false) + WriteOkay(Okay, output, "Install:", inst, "\n"); + if (flags & Request::AUTOREMOVE) + WriteOkay(Okay, output, "Autoremove: yes\n"); + if (flags & Request::UPGRADE_ALL) + { + WriteOkay(Okay, output, "Upgrade-All: yes\n"); + if (flags & (Request::FORBID_NEW_INSTALL | Request::FORBID_REMOVE)) + WriteOkay(Okay, output, "Upgrade: yes\n"); + else + WriteOkay(Okay, output, "Dist-Upgrade: yes\n"); + } + if (flags & Request::FORBID_NEW_INSTALL) + WriteOkay(Okay, output, "Forbid-New-Install: yes\n"); + if (flags & Request::FORBID_REMOVE) + WriteOkay(Okay, output, "Forbid-Remove: yes\n"); + auto const solver = _config->Find("APT::Solver", "internal"); + WriteOkay(Okay, output, "Solver: ", solver, "\n"); + if (_config->FindB("APT::Solver::Strict-Pinning", true) == false) + WriteOkay(Okay, output, "Strict-Pinning: no\n"); + string solverpref("APT::Solver::"); + solverpref.append(solver).append("::Preferences"); + if (_config->Exists(solverpref) == true) + WriteOkay(Okay, output, "Preferences: ", _config->Find(solverpref,""), "\n"); + return WriteOkay(Okay, output, "\n"); +} + /*}}}*/ +// EDSP::ReadResponse - from the given file descriptor /*{{{*/ +bool EDSP::ReadResponse(int const input, pkgDepCache &Cache, OpProgress *Progress) { + /* We build an map id to mmap offset here + In theory we could use the offset as ID, but then VersionCount + couldn't be used to create other versionmappings anymore and it + would be too easy for a (buggy) solver to segfault APT… */ + auto VersionCount = Cache.Head().VersionCount; + decltype(VersionCount) VerIdx[VersionCount]; + for (pkgCache::PkgIterator P = Cache.PkgBegin(); P.end() == false; ++P) { + for (pkgCache::VerIterator V = P.VersionList(); V.end() == false; ++V) + VerIdx[V->ID] = V.Index(); + Cache[P].Marked = true; + Cache[P].Garbage = false; + } + + FileFd in; + in.OpenDescriptor(input, FileFd::ReadOnly, true); + pkgTagFile response(&in, 100); + pkgTagSection section; + + std::set<decltype(Cache.PkgBegin()->ID)> seenOnce; + while (response.Step(section) == true) { + std::string type; + if (section.Exists("Install") == true) + type = "Install"; + else if (section.Exists("Remove") == true) + type = "Remove"; + else if (section.Exists("Progress") == true) { + if (Progress != NULL) { + string msg = section.FindS("Message"); + if (msg.empty() == true) + msg = _("Prepare for receiving solution"); + Progress->SubProgress(100, msg, section.FindI("Percentage", 0)); + } + continue; + } else if (section.Exists("Error") == true) { + if (_error->PendingError()) { + if (Progress != nullptr) + Progress->Done(); + Progress = nullptr; + _error->DumpErrors(std::cerr, GlobalError::DEBUG, false); + } + std::string msg = SubstVar(SubstVar(section.FindS("Message"), "\n .\n", "\n\n"), "\n ", "\n"); + if (msg.empty() == true) { + msg = _("External solver failed without a proper error message"); + _error->Error("%s", msg.c_str()); + } else + _error->Error("External solver failed with: %s", msg.substr(0,msg.find('\n')).c_str()); + if (Progress != nullptr) + Progress->Done(); + std::cerr << "The solver encountered an error of type: " << section.FindS("Error") << std::endl; + std::cerr << "The following information might help you to understand what is wrong:" << std::endl; + std::cerr << msg << std::endl << std::endl; + return false; + } else if (section.Exists("Autoremove") == true) + type = "Autoremove"; + else { + char const *Start, *End; + section.GetSection(Start, End); + _error->Warning("Encountered an unexpected section with %d fields: %s", section.Count(), std::string(Start, End).c_str()); + continue; + } + + decltype(VersionCount) const id = section.FindULL(type.c_str(), VersionCount); + if (id == VersionCount) { + _error->Warning("Unable to parse %s request with id value '%s'!", type.c_str(), section.FindS(type.c_str()).c_str()); + continue; + } else if (id > VersionCount) { + _error->Warning("ID value '%s' in %s request stanza is to high to refer to a known version!", section.FindS(type.c_str()).c_str(), type.c_str()); + continue; + } + + pkgCache::VerIterator Ver(Cache.GetCache(), Cache.GetCache().VerP + VerIdx[id]); + auto const Pkg = Ver.ParentPkg(); + if (type == "Autoremove") { + Cache[Pkg].Marked = false; + Cache[Pkg].Garbage = true; + } else if (seenOnce.emplace(Pkg->ID).second == false) { + _error->Warning("Ignoring %s stanza received for package %s which already had a previous stanza effecting it!", type.c_str(), Pkg.FullName(false).c_str()); + } else if (type == "Install") { + if (Pkg.CurrentVer() == Ver) { + _error->Warning("Ignoring Install stanza received for version %s of package %s which is already installed!", + Ver.VerStr(), Pkg.FullName(false).c_str()); + } else { + Cache.SetCandidateVersion(Ver); + Cache.MarkInstall(Pkg, false, 0, false); + } + } else if (type == "Remove") { + if (Pkg->CurrentVer == 0) + _error->Warning("Ignoring Remove stanza received for version %s of package %s which isn't installed!", + Ver.VerStr(), Pkg.FullName(false).c_str()); + else if (Pkg.CurrentVer() != Ver) + _error->Warning("Ignoring Remove stanza received for version %s of package %s which isn't the installed version %s!", + Ver.VerStr(), Pkg.FullName(false).c_str(), Pkg.CurrentVer().VerStr()); + else + Cache.MarkDelete(Ver.ParentPkg(), false); + } + } + return true; +} + /*}}}*/ +// ReadLine - first line from the given file descriptor /*{{{*/ +// --------------------------------------------------------------------- +/* Little helper method to read a complete line into a string. Similar to + fgets but we need to use the low-level read() here as otherwise the + listparser will be confused later on as mixing of fgets and read isn't + a supported action according to the manpages and results are undefined */ +static bool ReadLine(int const input, std::string &line) { + char one; + ssize_t data = 0; + line.erase(); + line.reserve(100); + while ((data = read(input, &one, sizeof(one))) != -1) { + if (data != 1) + continue; + if (one == '\n') + return true; + if (one == '\r') + continue; + if (line.empty() == true && isblank(one) != 0) + continue; + line += one; + } + return false; +} + /*}}}*/ +// StringToBool - convert yes/no to bool /*{{{*/ +// --------------------------------------------------------------------- +/* we are not as lazy as we are in the global StringToBool as we really + only accept yes/no here */ +static bool localStringToBool(std::string answer, bool const defValue) { + std::transform(answer.begin(), answer.end(), answer.begin(), ::tolower); + if (answer == "yes") + return true; + else if (answer == "no") + return false; + else + _error->Warning("Value '%s' is not a boolean 'yes' or 'no'!", answer.c_str()); + return defValue; +} + /*}}}*/ +static bool LineStartsWithAndStrip(std::string &line, APT::StringView const with)/*{{{*/ +{ + if (line.compare(0, with.size(), with.data()) != 0) + return false; + line = APT::String::Strip(line.substr(with.length())); + return true; +} + /*}}}*/ +static bool ReadFlag(unsigned int &flags, std::string &line, APT::StringView const name, unsigned int const setflag)/*{{{*/ +{ + if (LineStartsWithAndStrip(line, name) == false) + return false; + if (localStringToBool(line, false)) + flags |= setflag; + else + flags &= ~setflag; + return true; +} + /*}}}*/ +// EDSP::ReadRequest - first stanza from the given file descriptor /*{{{*/ +bool EDSP::ReadRequest(int const input, std::list<std::string> &install, + std::list<std::string> &remove, unsigned int &flags) +{ + install.clear(); + remove.clear(); + flags = 0; + std::string line; + while (ReadLine(input, line) == true) + { + // Skip empty lines before request + if (line.empty() == true) + continue; + // The first Tag must be a request, so search for it + if (LineStartsWithAndStrip(line, "Request:")) + continue; + + while (ReadLine(input, line) == true) + { + // empty lines are the end of the request + if (line.empty() == true) + return true; + + std::list<std::string> *request = NULL; + if (LineStartsWithAndStrip(line, "Install:")) + request = &install; + else if (LineStartsWithAndStrip(line, "Remove:")) + request = &remove; + else if (ReadFlag(flags, line, "Upgrade:", (Request::UPGRADE_ALL | Request::FORBID_REMOVE | Request::FORBID_NEW_INSTALL)) || + ReadFlag(flags, line, "Dist-Upgrade:", Request::UPGRADE_ALL) || + ReadFlag(flags, line, "Upgrade-All:", Request::UPGRADE_ALL) || + ReadFlag(flags, line, "Forbid-New-Install:", Request::FORBID_NEW_INSTALL) || + ReadFlag(flags, line, "Forbid-Remove:", Request::FORBID_REMOVE) || + ReadFlag(flags, line, "Autoremove:", Request::AUTOREMOVE)) + ; + else if (LineStartsWithAndStrip(line, "Architecture:")) + _config->Set("APT::Architecture", line); + else if (LineStartsWithAndStrip(line, "Architectures:")) + _config->Set("APT::Architectures", SubstVar(line, " ", ",")); + else if (LineStartsWithAndStrip(line, "Solver:")) + ; // purely informational line + else + _error->Warning("Unknown line in EDSP Request stanza: %s", line.c_str()); + + if (request == NULL) + continue; + auto const pkgs = VectorizeString(line, ' '); + std::move(pkgs.begin(), pkgs.end(), std::back_inserter(*request)); + } + } + return false; +} +bool EDSP::ReadRequest(int const input, std::list<std::string> &install, + std::list<std::string> &remove, bool &upgrade, + bool &distUpgrade, bool &autoRemove) +{ + unsigned int flags; + auto const ret = ReadRequest(input, install, remove, flags); + autoRemove = (flags & Request::AUTOREMOVE); + if (flags & Request::UPGRADE_ALL) + { + if (flags & (Request::FORBID_NEW_INSTALL | Request::FORBID_REMOVE)) + { + upgrade = true; + distUpgrade = false; + } else { + upgrade = false; + distUpgrade = false; + } + } + else + { + upgrade = false; + distUpgrade = false; + } + return ret; +} + /*}}}*/ +// EDSP::ApplyRequest - first stanza from the given file descriptor /*{{{*/ +bool EDSP::ApplyRequest(std::list<std::string> const &install, + std::list<std::string> const &remove, + pkgDepCache &Cache) +{ + for (std::list<std::string>::const_iterator i = install.begin(); + i != install.end(); ++i) { + pkgCache::PkgIterator P = Cache.FindPkg(*i); + if (P.end() == true) + _error->Warning("Package %s is not known, so can't be installed", i->c_str()); + else + Cache.MarkInstall(P, false); + } + + for (std::list<std::string>::const_iterator i = remove.begin(); + i != remove.end(); ++i) { + pkgCache::PkgIterator P = Cache.FindPkg(*i); + if (P.end() == true) + _error->Warning("Package %s is not known, so can't be installed", i->c_str()); + else + Cache.MarkDelete(P); + } + return true; +} + /*}}}*/ +// EDSP::WriteSolutionStanza - to the given file descriptor /*{{{*/ +bool EDSP::WriteSolution(pkgDepCache &Cache, FILE* output) +{ + bool const Debug = _config->FindB("Debug::EDSP::WriteSolution", false); + for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg) + { + if (Cache[Pkg].Delete() == true) + { + fprintf(output, "Remove: %d\n", _system->GetVersionMapping(Pkg.CurrentVer()->ID)); + if (Debug == true) + fprintf(output, "Package: %s\nVersion: %s\n", Pkg.FullName().c_str(), Pkg.CurrentVer().VerStr()); + } + else if (Cache[Pkg].NewInstall() == true || Cache[Pkg].Upgrade() == true) + { + pkgCache::VerIterator const CandVer = Cache.GetCandidateVersion(Pkg); + fprintf(output, "Install: %d\n", _system->GetVersionMapping(CandVer->ID)); + if (Debug == true) + fprintf(output, "Package: %s\nVersion: %s\n", Pkg.FullName().c_str(), CandVer.VerStr()); + } + else if (Cache[Pkg].Garbage == true) + { + fprintf(output, "Autoremove: %d\n", _system->GetVersionMapping(Pkg.CurrentVer()->ID)); + if (Debug == true) + fprintf(output, "Package: %s\nVersion: %s\n", Pkg.FullName().c_str(), Pkg.CurrentVer().VerStr()); + } + else + continue; + fprintf(output, "\n"); + } + + return true; +} +bool EDSP::WriteSolutionStanza(FileFd &output, char const * const Type, pkgCache::VerIterator const &Ver) +{ + bool Okay = output.Failed() == false; + WriteOkay(Okay, output, Type, ": ", _system->GetVersionMapping(Ver->ID)); + if (_config->FindB("Debug::EDSP::WriteSolution", false) == true) + WriteOkay(Okay, output, "\nPackage: ", Ver.ParentPkg().FullName(), "\nVersion: ", Ver.VerStr()); + return WriteOkay(Okay, output, "\n\n"); +} + /*}}}*/ +// EDSP::WriteProgess - pulse to the given file descriptor /*{{{*/ +bool EDSP::WriteProgress(unsigned short const percent, const char* const message, FILE* output) { + fprintf(output, "Progress: %s\n", TimeRFC1123(time(NULL), true).c_str()); + fprintf(output, "Percentage: %d\n", percent); + fprintf(output, "Message: %s\n\n", message); + fflush(output); + return true; +} +bool EDSP::WriteProgress(unsigned short const percent, const char* const message, FileFd &output) { + return WriteOkay(output, "Progress: ", TimeRFC1123(time(NULL), true), "\n", + "Percentage: ", percent, "\n", + "Message: ", message, "\n\n") && output.Flush(); +} + /*}}}*/ +// EDSP::WriteError - format an error message to be send to file descriptor /*{{{*/ +static std::string formatMessage(std::string const &msg) +{ + return SubstVar(SubstVar(APT::String::Strip(msg), "\n\n", "\n.\n"), "\n", "\n "); +} +bool EDSP::WriteError(char const * const uuid, std::string const &message, FILE* output) { + fprintf(output, "Error: %s\n", uuid); + fprintf(output, "Message: %s\n\n", formatMessage(message).c_str()); + return true; +} +bool EDSP::WriteError(char const * const uuid, std::string const &message, FileFd &output) { + return WriteOkay(output, "Error: ", uuid, "\n", + "Message: ", formatMessage(message), + "\n\n"); +} + /*}}}*/ +static std::string findExecutable(std::vector<std::string> const &dirs, char const * const binary) {/*{{{*/ + for (auto && dir : dirs) { + std::string const file = flCombine(dir, binary); + if (RealFileExists(file) == true) + return file; + } + return ""; +} + /*}}}*/ +static pid_t ExecuteExternal(char const* const type, char const * const binary, char const * const configdir, int * const solver_in, int * const solver_out) {/*{{{*/ + auto const solverDirs = _config->FindVector(configdir); + auto const file = findExecutable(solverDirs, binary); + std::string dumper; + { + dumper = findExecutable(solverDirs, "apt-dump-solver"); + if (dumper.empty()) + dumper = findExecutable(solverDirs, "dump"); + } + + if (file.empty() == true) + { + _error->Error("Can't call external %s '%s' as it is not in a configured directory!", type, binary); + return 0; + } + int external[4] = {-1, -1, -1, -1}; + if (pipe(external) != 0 || pipe(external + 2) != 0) + { + _error->Errno("Resolve", "Can't create needed IPC pipes for EDSP"); + return 0; + } + for (int i = 0; i < 4; ++i) + SetCloseExec(external[i], true); + + pid_t Solver = ExecFork(); + if (Solver == 0) { + dup2(external[0], STDIN_FILENO); + dup2(external[3], STDOUT_FILENO); + auto const dumpfile = _config->FindFile((std::string("Dir::Log::") + type).c_str()); + auto const dumpdir = flNotFile(dumpfile); + auto const runasuser = _config->Find(std::string("APT::") + type + "::" + binary + "::RunAsUser", + _config->Find(std::string("APT::") + type + "::RunAsUser", + _config->Find("APT::Sandbox::User"))); + if (dumper.empty() || dumpfile.empty() || dumper == file || CreateAPTDirectoryIfNeeded(dumpdir, dumpdir) == false) + { + _config->Set("APT::Sandbox::User", runasuser); + DropPrivileges(); + char const * const calling[] = { file.c_str(), nullptr }; + execv(calling[0], const_cast<char**>(calling)); + } + else + { + char const * const calling[] = { dumper.c_str(), "--user", runasuser.c_str(), dumpfile.c_str(), file.c_str(), nullptr }; + execv(calling[0], const_cast<char**>(calling)); + } + std::cerr << "Failed to execute " << type << " '" << binary << "'!" << std::endl; + _exit(100); + } + close(external[0]); + close(external[3]); + + if (WaitFd(external[1], true, 5) == false) + { + _error->Errno("Resolve", "Timed out while Waiting on availability of %s stdin", type); + return 0; + } + + *solver_in = external[1]; + *solver_out = external[2]; + return Solver; +} + /*}}}*/ +// EDSP::ExecuteSolver - fork requested solver and setup ipc pipes {{{*/ +pid_t EDSP::ExecuteSolver(const char* const solver, int * const solver_in, int * const solver_out, bool) { + return ExecuteExternal("solver", solver, "Dir::Bin::Solvers", solver_in, solver_out); +} +bool EDSP::ExecuteSolver(const char* const solver, int *solver_in, int *solver_out) { + if (ExecuteSolver(solver, solver_in, solver_out, true) == 0) + return false; + return true; +} + /*}}}*/ +static bool CreateDumpFile(char const * const id, char const * const type, FileFd &output)/*{{{*/ +{ + auto const dumpfile = _config->FindFile((std::string("Dir::Log::") + type).c_str()); + if (dumpfile.empty()) + return false; + auto const dumpdir = flNotFile(dumpfile); + _error->PushToStack(); + bool errored_out = CreateAPTDirectoryIfNeeded(dumpdir, dumpdir) == false || + output.Open(dumpfile, FileFd::WriteOnly | FileFd::Exclusive | FileFd::Create, FileFd::Extension, 0644) == false; + std::vector<std::string> downgrademsgs; + while (_error->empty() == false) + { + std::string msg; + _error->PopMessage(msg); + downgrademsgs.emplace_back(std::move(msg)); + } + _error->RevertToStack(); + for (auto && msg : downgrademsgs) + _error->Warning("%s", msg.c_str()); + if (errored_out) + return _error->WarningE(id, _("Could not open file '%s'"), dumpfile.c_str()); + return true; +} + /*}}}*/ +// EDSP::ResolveExternal - resolve problems by asking external for help {{{*/ +bool EDSP::ResolveExternal(const char* const solver, pkgDepCache &Cache, + unsigned int const flags, OpProgress *Progress) { + if (strcmp(solver, "internal") == 0) + { + FileFd output; + bool Okay = CreateDumpFile("EDSP::Resolve", "solver", output); + Okay &= EDSP::WriteRequest(Cache, output, flags, nullptr); + return Okay && EDSP::WriteScenario(Cache, output, nullptr); + } + _error->PushToStack(); + int solver_in, solver_out; + pid_t const solver_pid = ExecuteSolver(solver, &solver_in, &solver_out, true); + if (solver_pid == 0) + return false; + + FileFd output; + if (output.OpenDescriptor(solver_in, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) + return _error->Errno("ResolveExternal", "Opening solver %s stdin on fd %d for writing failed", solver, solver_in); + + bool Okay = output.Failed() == false; + if (Okay && Progress != NULL) + Progress->OverallProgress(0, 100, 5, _("Execute external solver")); + Okay &= EDSP::WriteRequest(Cache, output, flags, Progress); + if (Okay && Progress != NULL) + Progress->OverallProgress(5, 100, 20, _("Execute external solver")); + Okay &= EDSP::WriteScenario(Cache, output, Progress); + output.Close(); + + if (Okay && Progress != NULL) + Progress->OverallProgress(25, 100, 75, _("Execute external solver")); + bool const ret = EDSP::ReadResponse(solver_out, Cache, Progress); + _error->MergeWithStack(); + if (ExecWait(solver_pid, solver)) + return ret; + return false; +} +bool EDSP::ResolveExternal(const char* const solver, pkgDepCache &Cache, + bool const upgrade, bool const distUpgrade, + bool const autoRemove, OpProgress *Progress) { + unsigned int flags = 0; + if (autoRemove) + flags |= Request::AUTOREMOVE; + if (upgrade) + flags |= Request::UPGRADE_ALL | Request::FORBID_REMOVE | Request::FORBID_NEW_INSTALL; + if (distUpgrade) + flags |= Request::UPGRADE_ALL; + return ResolveExternal(solver, Cache, flags, Progress); +} + /*}}}*/ + +bool EIPP::OrderInstall(char const * const solver, pkgPackageManager * const PM, /*{{{*/ + unsigned int const flags, OpProgress * const Progress) +{ + if (strcmp(solver, "internal") == 0) + { + FileFd output; + _error->PushToStack(); + bool Okay = CreateDumpFile("EIPP::OrderInstall", "planner", output); + if (Okay == false && dynamic_cast<pkgSimulate*>(PM) != nullptr) + { + _error->RevertToStack(); + return false; + } + _error->MergeWithStack(); + Okay &= EIPP::WriteRequest(PM->Cache, output, flags, nullptr); + return Okay && EIPP::WriteScenario(PM->Cache, output, nullptr); + } + _error->PushToStack(); + int solver_in, solver_out; + pid_t const solver_pid = ExecuteExternal("planner", solver, "Dir::Bin::Planners", &solver_in, &solver_out); + if (solver_pid == 0) + return false; + + FileFd output; + if (output.OpenDescriptor(solver_in, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) + return _error->Errno("EIPP::OrderInstall", "Opening planner %s stdin on fd %d for writing failed", solver, solver_in); + + bool Okay = output.Failed() == false; + if (Okay && Progress != NULL) + Progress->OverallProgress(0, 100, 5, _("Execute external planner")); + Okay &= EIPP::WriteRequest(PM->Cache, output, flags, Progress); + if (Okay && Progress != NULL) + Progress->OverallProgress(5, 100, 20, _("Execute external planner")); + Okay &= EIPP::WriteScenario(PM->Cache, output, Progress); + output.Close(); + + if (Okay) + { + if (Progress != nullptr) + Progress->OverallProgress(25, 100, 75, _("Execute external planner")); + + // we don't tell the external planners about boring things + for (auto Pkg = PM->Cache.PkgBegin(); Pkg.end() == false; ++Pkg) + { + if (Pkg->CurrentState == pkgCache::State::ConfigFiles && PM->Cache[Pkg].Purge() == true) + PM->Remove(Pkg, true); + } + } + bool const ret = EIPP::ReadResponse(solver_out, PM, Progress); + _error->MergeWithStack(); + if (ExecWait(solver_pid, solver)) + return ret; + return false; +} + /*}}}*/ +bool EIPP::WriteRequest(pkgDepCache &Cache, FileFd &output, /*{{{*/ + unsigned int const flags, + OpProgress * const Progress) +{ + if (Progress != NULL) + Progress->SubProgress(Cache.Head().PackageCount, _("Send request to planner")); + decltype(Cache.Head().PackageCount) p = 0; + string del, inst, reinst; + for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg, ++p) + { + if (Progress != NULL && p % 100 == 0) + Progress->Progress(p); + string* req; + pkgDepCache::StateCache &P = Cache[Pkg]; + if (P.Purge() == true && Pkg->CurrentState == pkgCache::State::ConfigFiles) + continue; + if (P.Delete() == true) + req = &del; + else if (P.NewInstall() == true || P.Upgrade() == true || P.Downgrade() == true) + req = &inst; + else if (P.ReInstall() == true) + req = &reinst; + else + continue; + req->append(" ").append(Pkg.FullName()); + } + bool Okay = WriteOkay(output, "Request: EIPP 0.1\n"); + + const char *arch = _config->Find("APT::Architecture").c_str(); + std::vector<string> archs = APT::Configuration::getArchitectures(); + WriteOkay(Okay, output, "Architecture: ", arch, "\n", + "Architectures:"); + for (std::vector<string>::const_iterator a = archs.begin(); a != archs.end(); ++a) + WriteOkay(Okay, output, " ", *a); + WriteOkay(Okay, output, "\n"); + + if (del.empty() == false) + WriteOkay(Okay, output, "Remove:", del, "\n"); + if (inst.empty() == false) + WriteOkay(Okay, output, "Install:", inst, "\n"); + if (reinst.empty() == false) + WriteOkay(Okay, output, "ReInstall:", reinst, "\n"); + WriteOkay(Okay, output, "Planner: ", _config->Find("APT::Planner", "internal"), "\n"); + if ((flags & Request::IMMEDIATE_CONFIGURATION_ALL) != 0) + WriteOkay(Okay, output, "Immediate-Configuration: yes\n"); + else if ((flags & Request::NO_IMMEDIATE_CONFIGURATION) != 0) + WriteOkay(Okay, output, "Immediate-Configuration: no\n"); + else if ((flags & Request::ALLOW_TEMPORARY_REMOVE_OF_ESSENTIALS) != 0) + WriteOkay(Okay, output, "Allow-Temporary-Remove-of-Essentials: yes\n"); + return WriteOkay(Okay, output, "\n"); +} + /*}}}*/ +static bool WriteScenarioEIPPVersion(pkgDepCache &, FileFd &output, pkgCache::PkgIterator const &Pkg,/*{{{*/ + pkgCache::VerIterator const &Ver) +{ + bool Okay = true; + if (Pkg.CurrentVer() == Ver) + switch (Pkg->CurrentState) + { + case pkgCache::State::NotInstalled: WriteOkay(Okay, output, "\nStatus: not-installed"); break; + case pkgCache::State::ConfigFiles: WriteOkay(Okay, output, "\nStatus: config-files"); break; + case pkgCache::State::HalfInstalled: WriteOkay(Okay, output, "\nStatus: half-installed"); break; + case pkgCache::State::UnPacked: WriteOkay(Okay, output, "\nStatus: unpacked"); break; + case pkgCache::State::HalfConfigured: WriteOkay(Okay, output, "\nStatus: half-configured"); break; + case pkgCache::State::TriggersAwaited: WriteOkay(Okay, output, "\nStatus: triggers-awaited"); break; + case pkgCache::State::TriggersPending: WriteOkay(Okay, output, "\nStatus: triggers-pending"); break; + case pkgCache::State::Installed: WriteOkay(Okay, output, "\nStatus: installed"); break; + } + return Okay; +} + /*}}}*/ +// EIPP::WriteScenario - to the given file descriptor /*{{{*/ +template<typename forVersion> void forAllInterestingVersions(pkgDepCache &Cache, pkgCache::PkgIterator const &Pkg, forVersion const &func) +{ + if (Pkg->CurrentState == pkgCache::State::NotInstalled) + { + auto P = Cache[Pkg]; + if (P.Install() == false) + return; + func(Pkg, P.InstVerIter(Cache)); + } + else + { + if (Pkg->CurrentVer != 0) + func(Pkg, Pkg.CurrentVer()); + auto P = Cache[Pkg]; + auto const V = P.InstVerIter(Cache); + if (P.Delete() == false && Pkg.CurrentVer() != V) + func(Pkg, V); + } +} + +bool EIPP::WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress * const Progress) +{ + if (Progress != NULL) + Progress->SubProgress(Cache.Head().PackageCount, _("Send scenario to planner")); + decltype(Cache.Head().PackageCount) p = 0; + bool Okay = output.Failed() == false; + std::vector<std::string> archs = APT::Configuration::getArchitectures(); + std::vector<bool> pkgset(Cache.Head().PackageCount, false); + auto const MarkVersion = [&](pkgCache::PkgIterator const &Pkg, pkgCache::VerIterator const &Ver) { + pkgset[Pkg->ID] = true; + for (auto D = Ver.DependsList(); D.end() == false; ++D) + { + if (D.IsCritical() == false) + continue; + auto const P = D.TargetPkg(); + for (auto Prv = P.ProvidesList(); Prv.end() == false; ++Prv) + { + auto const V = Prv.OwnerVer(); + auto const PV = V.ParentPkg(); + if (V == PV.CurrentVer() || V == Cache[PV].InstVerIter(Cache)) + pkgset[PV->ID] = true; + } + pkgset[P->ID] = true; + if (strcmp(P.Arch(), "any") == 0) + { + APT::StringView const pkgname(P.Name()); + auto const idxColon = pkgname.find(':'); + if (idxColon != APT::StringView::npos) + { + pkgCache::PkgIterator PA; + if (pkgname.substr(idxColon + 1) == "any") + { + auto const GA = Cache.FindGrp(pkgname.substr(0, idxColon).to_string()); + for (auto PA = GA.PackageList(); PA.end() == false; PA = GA.NextPkg(PA)) + { + pkgset[PA->ID] = true; + } + } + else + { + auto const PA = Cache.FindPkg(pkgname.to_string()); + if (PA.end() == false) + pkgset[PA->ID] = true; + } + } + } + else + { + auto const PA = Cache.FindPkg(P.FullName(false), "any"); + if (PA.end() == false) + pkgset[PA->ID] = true; + } + } + }; + for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg) + forAllInterestingVersions(Cache, Pkg, MarkVersion); + auto const WriteVersion = [&](pkgCache::PkgIterator const &Pkg, pkgCache::VerIterator const &Ver) { + Okay &= WriteScenarioVersion(output, Pkg, Ver); + Okay &= WriteScenarioEIPPVersion(Cache, output, Pkg, Ver); + Okay &= WriteScenarioLimitedDependency(output, Ver, pkgset, true); + WriteOkay(Okay, output, "\n"); + if (Progress != NULL && p % 100 == 0) + Progress->Progress(p); + }; + for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false && likely(Okay); ++Pkg, ++p) + { + if (pkgset[Pkg->ID] == false || Pkg->VersionList == 0) + continue; + forAllInterestingVersions(Cache, Pkg, WriteVersion); + } + return Okay; +} + /*}}}*/ +// EIPP::ReadResponse - from the given file descriptor /*{{{*/ +bool EIPP::ReadResponse(int const input, pkgPackageManager * const PM, OpProgress *Progress) { + /* We build an map id to mmap offset here + In theory we could use the offset as ID, but then VersionCount + couldn't be used to create other versionmappings anymore and it + would be too easy for a (buggy) solver to segfault APT… */ + auto VersionCount = PM->Cache.Head().VersionCount; + decltype(VersionCount) VerIdx[VersionCount]; + for (pkgCache::PkgIterator P = PM->Cache.PkgBegin(); P.end() == false; ++P) { + for (pkgCache::VerIterator V = P.VersionList(); V.end() == false; ++V) + VerIdx[V->ID] = V.Index(); + } + + FileFd in; + in.OpenDescriptor(input, FileFd::ReadOnly); + pkgTagFile response(&in, 100); + pkgTagSection section; + + while (response.Step(section) == true) { + char const * type = nullptr; + if (section.Exists("Progress") == true) { + if (Progress != NULL) { + string msg = section.FindS("Message"); + if (msg.empty() == true) + msg = _("Prepare for receiving solution"); + Progress->SubProgress(100, msg, section.FindI("Percentage", 0)); + } + continue; + } else if (section.Exists("Error") == true) { + if (_error->PendingError()) { + if (Progress != nullptr) + Progress->Done(); + Progress = nullptr; + _error->DumpErrors(std::cerr, GlobalError::DEBUG, false); + } + std::string msg = SubstVar(SubstVar(section.FindS("Message"), "\n .\n", "\n\n"), "\n ", "\n"); + if (msg.empty() == true) { + msg = _("External planner failed without a proper error message"); + _error->Error("%s", msg.c_str()); + } else + _error->Error("External planner failed with: %s", msg.substr(0,msg.find('\n')).c_str()); + if (Progress != nullptr) + Progress->Done(); + std::cerr << "The planner encountered an error of type: " << section.FindS("Error") << std::endl; + std::cerr << "The following information might help you to understand what is wrong:" << std::endl; + std::cerr << msg << std::endl << std::endl; + return false; + } else if (section.Exists("Unpack") == true) + type = "Unpack"; + else if (section.Exists("Configure") == true) + type = "Configure"; + else if (section.Exists("Remove") == true) + type = "Remove"; + else { + char const *Start, *End; + section.GetSection(Start, End); + _error->Warning("Encountered an unexpected section with %d fields: %s", section.Count(), std::string(Start, End).c_str()); + continue; + } + + if (type == nullptr) + continue; + decltype(VersionCount) const id = section.FindULL(type, VersionCount); + if (id == VersionCount) { + _error->Warning("Unable to parse %s request with id value '%s'!", type, section.FindS(type).c_str()); + continue; + } else if (id > VersionCount) { + _error->Warning("ID value '%s' in %s request stanza is to high to refer to a known version!", section.FindS(type).c_str(), type); + continue; + } + + pkgCache::VerIterator Ver(PM->Cache.GetCache(), PM->Cache.GetCache().VerP + VerIdx[id]); + auto const Pkg = Ver.ParentPkg(); + if (strcmp(type, "Unpack") == 0) + PM->Install(Pkg, PM->FileNames[Pkg->ID]); + else if (strcmp(type, "Configure") == 0) + PM->Configure(Pkg); + else if (strcmp(type, "Remove") == 0) + PM->Remove(Pkg, PM->Cache[Pkg].Purge()); + } + return in.Failed() == false; +} + /*}}}*/ +bool EIPP::ReadRequest(int const input, std::list<std::pair<std::string,PKG_ACTION>> &actions,/*{{{*/ + unsigned int &flags) +{ + actions.clear(); + flags = 0; + std::string line; + while (ReadLine(input, line) == true) + { + // Skip empty lines before request + if (line.empty() == true) + continue; + // The first Tag must be a request, so search for it + if (line.compare(0, 8, "Request:") != 0) + continue; + + while (ReadLine(input, line) == true) + { + // empty lines are the end of the request + if (line.empty() == true) + return true; + + PKG_ACTION pkgact = PKG_ACTION::NOOP; + if (LineStartsWithAndStrip(line, "Install:")) + pkgact = PKG_ACTION::INSTALL; + else if (LineStartsWithAndStrip(line, "ReInstall:")) + pkgact = PKG_ACTION::REINSTALL; + else if (LineStartsWithAndStrip(line, "Remove:")) + pkgact = PKG_ACTION::REMOVE; + else if (LineStartsWithAndStrip(line, "Architecture:")) + _config->Set("APT::Architecture", line); + else if (LineStartsWithAndStrip(line, "Architectures:")) + _config->Set("APT::Architectures", SubstVar(line, " ", ",")); + else if (LineStartsWithAndStrip(line, "Planner:")) + ; // purely informational line + else if (LineStartsWithAndStrip(line, "Immediate-Configuration:")) + { + if (localStringToBool(line, true)) + flags |= Request::IMMEDIATE_CONFIGURATION_ALL; + else + flags |= Request::NO_IMMEDIATE_CONFIGURATION; + } + else if (ReadFlag(flags, line, "Allow-Temporary-Remove-of-Essentials:", Request::ALLOW_TEMPORARY_REMOVE_OF_ESSENTIALS)) + ; + else + _error->Warning("Unknown line in EIPP Request stanza: %s", line.c_str()); + + if (pkgact == PKG_ACTION::NOOP) + continue; + for (auto && p: VectorizeString(line, ' ')) + actions.emplace_back(std::move(p), pkgact); + } + } + return false; +} + /*}}}*/ +bool EIPP::ApplyRequest(std::list<std::pair<std::string,PKG_ACTION>> &actions,/*{{{*/ + pkgDepCache &Cache) +{ + for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg) + { + short versions = 0; + for (auto Ver = Pkg.VersionList(); Ver.end() == false; ++Ver) + { + ++versions; + if (Pkg.CurrentVer() == Ver) + continue; + Cache.SetCandidateVersion(Ver); + } + if (unlikely(versions > 2)) + _error->Warning("Package %s has %d versions, but should have at most 2!", Pkg.FullName().c_str(), versions); + } + for (auto && a: actions) + { + pkgCache::PkgIterator P = Cache.FindPkg(a.first); + if (P.end() == true) + { + _error->Warning("Package %s is not known, so can't be acted on", a.first.c_str()); + continue; + } + switch (a.second) + { + case PKG_ACTION::NOOP: + _error->Warning("Package %s has NOOP as action?!?", a.first.c_str()); + break; + case PKG_ACTION::INSTALL: + Cache.MarkInstall(P, false); + break; + case PKG_ACTION::REINSTALL: + Cache.MarkInstall(P, false); + Cache.SetReInstall(P, true); + break; + case PKG_ACTION::REMOVE: + Cache.MarkDelete(P); + break; + } + } + return true; +} + /*}}}*/ diff --git a/apt-pkg/edsp.h b/apt-pkg/edsp.h new file mode 100644 index 0000000..2f18d76 --- /dev/null +++ b/apt-pkg/edsp.h @@ -0,0 +1,275 @@ +// -*- mode: cpp; mode: fold -*- +/** Description \file edsp.h {{{ + ###################################################################### + Set of methods to help writing and reading everything needed for EDSP + with the notable exception of reading a scenario for conversion into + a Cache as this is handled by edsp interface for listparser and friends + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_EDSP_H +#define PKGLIB_EDSP_H + +#include <apt-pkg/cacheset.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#include <stdio.h> + +#include <list> +#include <string> +#include <vector> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/depcache.h> +#include <apt-pkg/progress.h> +#endif + +class pkgDepCache; +class OpProgress; + +namespace EDSP /*{{{*/ +{ + namespace Request + { + enum Flags + { + AUTOREMOVE = (1 << 0), /*!< removal of unneeded packages should be performed */ + UPGRADE_ALL = (1 << 1), /*!< upgrade all installed packages, like 'apt-get full-upgrade' without forbid flags */ + FORBID_NEW_INSTALL = (1 << 2), /*!< forbid the resolver to install new packages */ + FORBID_REMOVE = (1 << 3), /*!< forbid the resolver to remove packages */ + }; + } + /** \brief creates the EDSP request stanza + * + * In the EDSP protocol the first thing send to the resolver is a stanza + * encoding the request. This method will write this stanza by looking at + * the given Cache and requests the installation of all packages which were + * marked for installation in it (equally for remove). + * + * \param Cache in which the request is encoded + * \param output is written to this "file" + * \param flags effecting the request documented in #EDSP::Request::Flags + * \param Progress is an instance to report progress to + * + * \return true if request was composed successfully, otherwise false + */ + bool WriteRequest(pkgDepCache &Cache, FileFd &output, + unsigned int const flags = 0, + OpProgress *Progress = NULL); + bool WriteRequest(pkgDepCache &Cache, FILE* output, + bool const upgrade = false, + bool const distUpgrade = false, + bool const autoRemove = false, + OpProgress *Progress = NULL) APT_DEPRECATED_MSG("Use FileFd-based interface instead"); + + /** \brief creates the scenario representing the package universe + * + * After the request all known information about a package are send + * to the solver. The output looks similar to a Packages or status file + * + * All packages and version included in this Cache are send, even if + * it doesn't make sense from an APT resolver point of view like versions + * with a negative pin to enable the solver to propose even that as a + * solution or at least to be able to give a hint what can be done to + * satisfy a request. + * + * \param Cache is the known package universe + * \param output is written to this "file" + * \param Progress is an instance to report progress to + * + * \return true if universe was composed successfully, otherwise false + */ + bool WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress *Progress = NULL); + bool WriteScenario(pkgDepCache &Cache, FILE* output, OpProgress *Progress = NULL) APT_DEPRECATED_MSG("Use FileFd-based interface instead"); + + /** \brief creates a limited scenario representing the package universe + * + * This method works similar to #WriteScenario as it works in the same + * way but doesn't send the complete universe to the solver but only + * packages included in the pkgset which will have only dependencies + * on packages which are in the given set. All other dependencies will + * be removed, so that this method can be used to create testcases + * + * \param Cache is the known package universe + * \param output is written to this "file" + * \param pkgset is a set of packages the universe should be limited to + * \param Progress is an instance to report progress to + * + * \return true if universe was composed successfully, otherwise false + */ + bool WriteLimitedScenario(pkgDepCache &Cache, FileFd &output, + std::vector<bool> const &pkgset, + OpProgress *Progress = NULL); + bool WriteLimitedScenario(pkgDepCache &Cache, FILE* output, + APT::PackageSet const &pkgset, + OpProgress *Progress = NULL) APT_DEPRECATED_MSG("Use FileFd-based interface instead"); + + /** \brief waits and acts on the information returned from the solver + * + * This method takes care of interpreting whatever the solver sends + * through the standard output like a solution, progress or an error. + * The main thread should hand his control over to this method to + * wait for the solver to finish the given task. The file descriptor + * used as input is completely consumed and closed by the method. + * + * \param input file descriptor with the response from the solver + * \param Cache the solution should be applied on if any + * \param Progress is an instance to report progress to + * + * \return true if a solution is found and applied correctly, otherwise false + */ + bool ReadResponse(int const input, pkgDepCache &Cache, OpProgress *Progress = NULL); + + /** \brief search and read the request stanza for action later + * + * This method while ignore the input up to the point it finds the + * Request: line as an indicator for the Request stanza. + * The request is stored in the parameters install and remove then, + * as the cache isn't build yet as the scenario follows the request. + * + * \param input file descriptor with the edsp input for the solver + * \param[out] install is a list which gets populated with requested installs + * \param[out] remove is a list which gets populated with requested removals + * \param[out] upgrade is true if it is a request like apt-get upgrade + * \param[out] distUpgrade is true if it is a request like apt-get dist-upgrade + * \param[out] autoRemove is true if removal of unneeded packages should be performed + * + * \return true if the request could be found and worked on, otherwise false + */ + bool ReadRequest(int const input, std::list<std::string> &install, + std::list<std::string> &remove, unsigned int &flags); + APT_DEPRECATED_MSG("use the flag-based version instead") bool ReadRequest(int const input, std::list<std::string> &install, + std::list<std::string> &remove, bool &upgrade, + bool &distUpgrade, bool &autoRemove); + + /** \brief takes the request lists and applies it on the cache + * + * The lists as created by #ReadRequest will be used to find the + * packages in question and mark them for install/remove. + * No solving is done and no auto-install/-remove. + * + * \param install is a list of packages to mark for installation + * \param remove is a list of packages to mark for removal + * \param Cache is there the markers should be set + * + * \return false if the request couldn't be applied, true otherwise + */ + bool ApplyRequest(std::list<std::string> const &install, + std::list<std::string> const &remove, + pkgDepCache &Cache); + + /** \brief formats a solution stanza for the given version + * + * EDSP uses a simple format for reporting solutions: + * A single required field name with an ID as value. + * Additional fields might appear as debug aids. + * + * \param output to write the stanza forming the solution to + * \param Type of the stanza, used as field name + * \param Ver this stanza applies to + * + * \return true if stanza could be written, otherwise false + */ + bool WriteSolutionStanza(FileFd &output, char const * const Type, pkgCache::VerIterator const &Ver); + bool WriteSolution(pkgDepCache &Cache, FILE* output) APT_DEPRECATED_MSG("Use FileFd-based single-stanza interface instead"); + + /** \brief sends a progress report + * + * \param percent of the solving completed + * \param message the solver wants the user to see + * \param output the front-end listens for progress report + */ + bool WriteProgress(unsigned short const percent, const char* const message, FileFd &output); + bool WriteProgress(unsigned short const percent, const char* const message, FILE* output) APT_DEPRECATED_MSG("Use FileFd-based interface instead"); + + /** \brief sends an error report + * + * Solvers are expected to execute successfully even if + * they were unable to calculate a solution for a given task. + * Obviously they can't send a solution through, so this + * methods deals with formatting an error message correctly + * so that the front-ends can receive and display it. + * + * The first line of the message should be a short description + * of the error so it can be used for dialog titles or alike + * + * \param uuid of this error message + * \param message is free form text to describe the error + * \param output the front-end listens for error messages + */ + bool WriteError(char const * const uuid, std::string const &message, FileFd &output); + bool WriteError(char const * const uuid, std::string const &message, FILE* output) APT_DEPRECATED_MSG("Use FileFd-based interface instead"); + + + /** \brief executes the given solver and returns the pipe ends + * + * The given solver is executed if it can be found in one of the + * configured directories and setup for it is performed. + * + * \param solver to execute + * \param[out] solver_in will be the stdin of the solver + * \param[out] solver_out will be the stdout of the solver + * + * \return PID of the started solver or 0 if failure occurred + */ + pid_t ExecuteSolver(const char* const solver, int * const solver_in, int * const solver_out, bool /*overload*/); + APT_DEPRECATED_MSG("add a dummy bool parameter to use the overload returning a pid_t") bool ExecuteSolver(const char* const solver, int *solver_in, int *solver_out); + + /** \brief call an external resolver to handle the request + * + * This method wraps all the methods above to call an external solver + * + * \param solver to execute + * \param Cache with the problem and as universe to work in + * \param flags effecting the request documented in #EDSP::Request::Flags + * \param Progress is an instance to report progress to + * + * \return true if the solver has successfully solved the problem, + * otherwise false + */ + bool ResolveExternal(const char* const solver, pkgDepCache &Cache, + unsigned int const flags = 0, + OpProgress *Progress = NULL); + APT_DEPRECATED_MSG("use the flag-based version instead") bool ResolveExternal(const char* const solver, pkgDepCache &Cache, + bool const upgrade, bool const distUpgrade, + bool const autoRemove, OpProgress *Progress = NULL); +} + /*}}}*/ +class pkgPackageManager; +namespace EIPP /*{{{*/ +{ + namespace Request + { + enum Flags + { + IMMEDIATE_CONFIGURATION_ALL = (1 << 0), /*!< try to keep the least amount of packages unconfigured as possible at all times */ + NO_IMMEDIATE_CONFIGURATION = (1 << 1), /*!< do not perform immediate configuration at all */ + ALLOW_TEMPORARY_REMOVE_OF_ESSENTIALS = (1 << 2), /*!< just as the name suggests, very special case and dangerous! */ + }; + } + + APT_HIDDEN bool WriteRequest(pkgDepCache &Cache, FileFd &output, + unsigned int const flags, OpProgress * const Progress); + APT_HIDDEN bool WriteScenario(pkgDepCache &Cache, FileFd &output, + OpProgress * const Progress); + + APT_HIDDEN bool OrderInstall(char const * const planner, pkgPackageManager * const PM, + unsigned int const version, OpProgress * const Progress); + APT_HIDDEN bool ReadResponse(int const input, pkgPackageManager * const PM, + OpProgress * const Progress); + + enum class PKG_ACTION + { + NOOP, + INSTALL, + REINSTALL, + REMOVE + }; + bool ReadRequest(int const input, + std::list<std::pair<std::string,PKG_ACTION>> &actions, + unsigned int &flags); + bool ApplyRequest(std::list<std::pair<std::string,PKG_ACTION>> &actions, + pkgDepCache &Cache); +} + /*}}}*/ +#endif diff --git a/apt-pkg/edsp/edspindexfile.cc b/apt-pkg/edsp/edspindexfile.cc new file mode 100644 index 0000000..1a8af89 --- /dev/null +++ b/apt-pkg/edsp/edspindexfile.cc @@ -0,0 +1,129 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + The scenario file is designed to work as an intermediate file between + APT and the resolver. Its on propose very similar to a dpkg status file + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/edspindexfile.h> +#include <apt-pkg/edsplistparser.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> + +#include <memory> +#include <string> +#include <stddef.h> +#include <unistd.h> + /*}}}*/ + +// EDSP-like Index /*{{{*/ +edspLikeIndex::edspLikeIndex(std::string const &File) : pkgDebianIndexRealFile(File, true) +{ +} +std::string edspLikeIndex::GetArchitecture() const +{ + return std::string(); +} +bool edspLikeIndex::HasPackages() const +{ + return true; +} +bool edspLikeIndex::Exists() const +{ + return true; +} +uint8_t edspLikeIndex::GetIndexFlags() const +{ + return 0; +} +bool edspLikeIndex::OpenListFile(FileFd &Pkg, std::string const &FileName) +{ + if (FileName.empty() == false && FileName != "/nonexistent/stdin") + return pkgDebianIndexRealFile::OpenListFile(Pkg, FileName); + if (Pkg.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false) + return _error->Error("Problem opening %s",FileName.c_str()); + return true; +} + /*}}}*/ +// EDSP Index /*{{{*/ +edspIndex::edspIndex(std::string const &File) : edspLikeIndex(File) +{ +} +std::string edspIndex::GetComponent() const +{ + return "edsp"; +} +pkgCacheListParser * edspIndex::CreateListParser(FileFd &Pkg) +{ + if (Pkg.IsOpen() == false) + return nullptr; + _error->PushToStack(); + std::unique_ptr<pkgCacheListParser> Parser(new edspListParser(&Pkg)); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + return newError ? nullptr : Parser.release(); +} + /*}}}*/ +// EIPP Index /*{{{*/ +eippIndex::eippIndex(std::string const &File) : edspLikeIndex(File) +{ +} +std::string eippIndex::GetComponent() const +{ + return "eipp"; +} +pkgCacheListParser * eippIndex::CreateListParser(FileFd &Pkg) +{ + if (Pkg.IsOpen() == false) + return nullptr; + _error->PushToStack(); + std::unique_ptr<pkgCacheListParser> Parser(new eippListParser(&Pkg)); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + return newError ? nullptr : Parser.release(); +} + /*}}}*/ + +// Index File types for APT /*{{{*/ +class APT_HIDDEN edspIFType: public pkgIndexFile::Type +{ + public: + virtual pkgRecords::Parser *CreatePkgParser(pkgCache::PkgFileIterator const &) const APT_OVERRIDE + { + // we don't have a record parser for this type as the file is not presistent + return NULL; + }; + edspIFType() {Label = "EDSP scenario file";}; +}; +APT_HIDDEN edspIFType _apt_Edsp; +const pkgIndexFile::Type *edspIndex::GetType() const +{ + return &_apt_Edsp; +} + +class APT_HIDDEN eippIFType: public pkgIndexFile::Type +{ + public: + virtual pkgRecords::Parser *CreatePkgParser(pkgCache::PkgFileIterator const &) const APT_OVERRIDE + { + // we don't have a record parser for this type as the file is not presistent + return NULL; + }; + eippIFType() {Label = "EIPP scenario file";}; +}; +APT_HIDDEN eippIFType _apt_Eipp; +const pkgIndexFile::Type *eippIndex::GetType() const +{ + return &_apt_Eipp; +} + /*}}}*/ + +edspLikeIndex::~edspLikeIndex() {} +edspIndex::~edspIndex() {} +eippIndex::~eippIndex() {} diff --git a/apt-pkg/edsp/edspindexfile.h b/apt-pkg/edsp/edspindexfile.h new file mode 100644 index 0000000..4ac0f9d --- /dev/null +++ b/apt-pkg/edsp/edspindexfile.h @@ -0,0 +1,62 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + The scenario file is designed to work as an intermediate file between + APT and the resolver. Its on propose very similar to a dpkg status file + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_EDSPINDEXFILE_H +#define PKGLIB_EDSPINDEXFILE_H + +#include <apt-pkg/debindexfile.h> +#include <string> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/indexfile.h> +#endif + +class OpProgress; +class pkgCacheGenerator; + +class APT_HIDDEN edspLikeIndex : public pkgDebianIndexRealFile +{ +protected: + virtual bool OpenListFile(FileFd &Pkg, std::string const &File) APT_OVERRIDE; + virtual uint8_t GetIndexFlags() const APT_OVERRIDE; + virtual std::string GetArchitecture() const APT_OVERRIDE; + +public: + virtual bool Exists() const APT_OVERRIDE; + virtual bool HasPackages() const APT_OVERRIDE; + + edspLikeIndex(std::string const &File); + virtual ~edspLikeIndex(); +}; + +class APT_HIDDEN edspIndex : public edspLikeIndex +{ +protected: + APT_HIDDEN virtual pkgCacheListParser * CreateListParser(FileFd &Pkg) APT_OVERRIDE; + virtual std::string GetComponent() const APT_OVERRIDE; + +public: + virtual const Type *GetType() const APT_OVERRIDE APT_PURE; + + edspIndex(std::string const &File); + virtual ~edspIndex(); +}; + +class APT_HIDDEN eippIndex : public edspLikeIndex +{ +protected: + APT_HIDDEN virtual pkgCacheListParser * CreateListParser(FileFd &Pkg) APT_OVERRIDE; + virtual std::string GetComponent() const APT_OVERRIDE; + +public: + virtual const Type *GetType() const APT_OVERRIDE APT_PURE; + + eippIndex(std::string const &File); + virtual ~eippIndex(); +}; + +#endif diff --git a/apt-pkg/edsp/edsplistparser.cc b/apt-pkg/edsp/edsplistparser.cc new file mode 100644 index 0000000..31bded9 --- /dev/null +++ b/apt-pkg/edsp/edsplistparser.cc @@ -0,0 +1,178 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Package Cache Generator - Generator for the cache structure. + + This builds the cache structure from the abstract package list parser. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/deblistparser.h> +#include <apt-pkg/edsplistparser.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/md5.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgsystem.h> +#include <apt-pkg/string_view.h> +#include <apt-pkg/tagfile.h> + +#include <array> + + /*}}}*/ + +// ListParser::edspListParser - Constructor /*{{{*/ +edspLikeListParser::edspLikeListParser(FileFd * const File) : debListParser(File) +{ +} +edspListParser::edspListParser(FileFd * const File) : edspLikeListParser(File) +{ + std::string const states = _config->FindFile("Dir::State::extended_states"); + RemoveFile("edspListParserPrivate", states); + extendedstates.Open(states, FileFd::WriteOnly | FileFd::Create | FileFd::Exclusive, 0600); + std::string const prefs = _config->FindFile("Dir::Etc::preferences"); + RemoveFile("edspListParserPrivate", prefs); + preferences.Open(prefs, FileFd::WriteOnly | FileFd::Create | FileFd::Exclusive, 0600); +} + /*}}}*/ +// ListParser::NewVersion - Fill in the version structure /*{{{*/ +bool edspLikeListParser::NewVersion(pkgCache::VerIterator &Ver) +{ + _system->SetVersionMapping(Ver->ID, Section.FindI("APT-ID", Ver->ID)); + return debListParser::NewVersion(Ver); +} + /*}}}*/ +// ListParser::Description - Return the description string /*{{{*/ +// --------------------------------------------------------------------- +/* Sorry, no description for the resolvers… */ +std::vector<std::string> edspLikeListParser::AvailableDescriptionLanguages() +{ + return {}; +} +APT::StringView edspLikeListParser::Description_md5() +{ + return APT::StringView(); +} + /*}}}*/ +// ListParser::VersionHash - Compute a unique hash for this version /*{{{*/ +unsigned short edspLikeListParser::VersionHash() +{ + if (Section.Exists("APT-Hash") == true) + return Section.FindI("APT-Hash"); + else if (Section.Exists("APT-ID") == true) + return Section.FindI("APT-ID"); + return 0; +} + /*}}}*/ +// ListParser::ParseStatus - Parse the status field /*{{{*/ +// --------------------------------------------------------------------- +/* The Status: line here is not a normal dpkg one but just one which tells + use if the package is installed or not, where missing means not. */ +bool edspListParser::ParseStatus(pkgCache::PkgIterator &Pkg, + pkgCache::VerIterator &Ver) +{ + unsigned long state = 0; + if (Section.FindFlag("Hold",state,pkgCache::State::Hold) == false) + return false; + if (state != 0) + Pkg->SelectedState = pkgCache::State::Hold; + + state = 0; + if (Section.FindFlag("Installed",state,pkgCache::State::Installed) == false) + return false; + if (state != 0) + { + Pkg->CurrentState = pkgCache::State::Installed; + Pkg->CurrentVer = Ver.Index(); + } + + if (Section.FindB("APT-Automatic", false)) + { + std::string out; + strprintf(out, "Package: %s\nArchitecture: %s\nAuto-Installed: 1\n\n", Pkg.Name(), Pkg.Arch()); + if (extendedstates.Write(out.c_str(), out.length()) == false) + return false; + } + + // FIXME: Using an overriding pin is wrong. + if (Section.FindB("APT-Candidate", false)) + { + std::string out; + strprintf(out, "Package: %s\nPin: version %s\nPin-Priority: 9999\n\n", Pkg.FullName().c_str(), Ver.VerStr()); + if (preferences.Write(out.c_str(), out.length()) == false) + return false; + } + + signed short const pinvalue = Section.FindI("APT-Pin", 500); + if (pinvalue != 500) + { + std::string out; + strprintf(out, "Package: %s\nPin: version %s\nPin-Priority: %d\n\n", Pkg.FullName().c_str(), Ver.VerStr(), pinvalue); + if (preferences.Write(out.c_str(), out.length()) == false) + return false; + } + + return true; +} + /*}}}*/ + +// ListParser::eippListParser - Constructor /*{{{*/ +eippListParser::eippListParser(FileFd *File) : edspLikeListParser(File) +{ +} + /*}}}*/ +// ListParser::ParseStatus - Parse the status field /*{{{*/ +// --------------------------------------------------------------------- +/* The Status: line here is not a normal dpkg one but just one which tells + use if the package is installed or not, where missing means not. */ +bool eippListParser::ParseStatus(pkgCache::PkgIterator &Pkg, + pkgCache::VerIterator &Ver) +{ + // Process the flag field + static std::array<WordList, 8> const statusvalues = {{ + {"not-installed",pkgCache::State::NotInstalled}, + {"config-files",pkgCache::State::ConfigFiles}, + {"half-installed",pkgCache::State::HalfInstalled}, + {"unpacked",pkgCache::State::UnPacked}, + {"half-configured",pkgCache::State::HalfConfigured}, + {"triggers-awaited",pkgCache::State::TriggersAwaited}, + {"triggers-pending",pkgCache::State::TriggersPending}, + {"installed",pkgCache::State::Installed}, + }}; + auto const status = Section.Find("Status"); + if (status.empty() == false) + { + for (auto && sv: statusvalues) + { + if (status != sv.Str) + continue; + Pkg->CurrentState = sv.Val; + switch (Pkg->CurrentState) + { + case pkgCache::State::NotInstalled: + case pkgCache::State::ConfigFiles: + break; + case pkgCache::State::HalfInstalled: + case pkgCache::State::UnPacked: + case pkgCache::State::HalfConfigured: + case pkgCache::State::TriggersAwaited: + case pkgCache::State::TriggersPending: + case pkgCache::State::Installed: + Pkg->CurrentVer = Ver.Index(); + break; + } + break; + } + } + + return true; +} + /*}}}*/ + +edspLikeListParser::~edspLikeListParser() {} +edspListParser::~edspListParser() {} +eippListParser::~eippListParser() {} diff --git a/apt-pkg/edsp/edsplistparser.h b/apt-pkg/edsp/edsplistparser.h new file mode 100644 index 0000000..59c759f --- /dev/null +++ b/apt-pkg/edsp/edsplistparser.h @@ -0,0 +1,64 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + EDSP Package List Parser - This implements the abstract parser + interface for the APT specific intermediate format which is passed + to external resolvers + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_EDSPLISTPARSER_H +#define PKGLIB_EDSPLISTPARSER_H + +#include <apt-pkg/deblistparser.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/md5.h> +#include <apt-pkg/pkgcache.h> + +#include <string> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/indexfile.h> +#include <apt-pkg/pkgcachegen.h> +#include <apt-pkg/tagfile.h> +#endif + +namespace APT { + class StringView; +} +class APT_HIDDEN edspLikeListParser : public debListParser +{ + public: + virtual bool NewVersion(pkgCache::VerIterator &Ver) APT_OVERRIDE; + virtual std::vector<std::string> AvailableDescriptionLanguages() APT_OVERRIDE; + virtual APT::StringView Description_md5() APT_OVERRIDE; + virtual unsigned short VersionHash() APT_OVERRIDE; + + edspLikeListParser(FileFd *File); + virtual ~edspLikeListParser(); +}; + +class APT_HIDDEN edspListParser : public edspLikeListParser +{ + FileFd extendedstates; + FileFd preferences; + +protected: + virtual bool ParseStatus(pkgCache::PkgIterator &Pkg,pkgCache::VerIterator &Ver) APT_OVERRIDE; + +public: + edspListParser(FileFd *File); + virtual ~edspListParser(); +}; + +class APT_HIDDEN eippListParser : public edspLikeListParser +{ +protected: + virtual bool ParseStatus(pkgCache::PkgIterator &Pkg,pkgCache::VerIterator &Ver) APT_OVERRIDE; + +public: + eippListParser(FileFd *File); + virtual ~eippListParser(); +}; +#endif diff --git a/apt-pkg/edsp/edspsystem.cc b/apt-pkg/edsp/edspsystem.cc new file mode 100644 index 0000000..47f5e06 --- /dev/null +++ b/apt-pkg/edsp/edspsystem.cc @@ -0,0 +1,166 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + This system provides the abstraction to use the scenario file as the + only source of package information to be able to feed the created file + back to APT for its own consumption (eat your own dogfood). + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/debversion.h> +#include <apt-pkg/edspindexfile.h> +#include <apt-pkg/edspsystem.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/pkgcache.h> + +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> + +#include <string> +#include <vector> + + /*}}}*/ + +// System::System - Constructor /*{{{*/ +edspLikeSystem::edspLikeSystem(char const * const Label) : pkgSystem(Label, &debVS) +{ +} +edspSystem::edspSystem() : edspLikeSystem("Debian APT solver interface") +{ +} +eippSystem::eippSystem() : edspLikeSystem("Debian APT planner interface") +{ +} + /*}}}*/ +// System::Lock - Get the lock /*{{{*/ +bool edspLikeSystem::Lock() +{ + return true; +} + /*}}}*/ +// System::UnLock - Drop a lock /*{{{*/ +bool edspLikeSystem::UnLock(bool /*NoErrors*/) +{ + return true; +} + /*}}}*/ +// System::CreatePM - Create the underlying package manager /*{{{*/ +// --------------------------------------------------------------------- +/* we can't use edsp input as input for real installations - just a + simulation can work, but everything else will fail bigtime */ +pkgPackageManager *edspLikeSystem::CreatePM(pkgDepCache * /*Cache*/) const +{ + return nullptr; +} + /*}}}*/ +// System::Initialize - Setup the configuration space.. /*{{{*/ +bool edspLikeSystem::Initialize(Configuration &Cnf) +{ + Cnf.Set("Dir::Log", "/dev/null"); + // state is included completely in the input files + Cnf.Set("Dir::Etc::preferences", "/dev/null"); + Cnf.Set("Dir::Etc::preferencesparts", "/dev/null"); + Cnf.Set("Dir::State::status","/dev/null"); + Cnf.Set("Dir::State::extended_states","/dev/null"); + Cnf.Set("Dir::State::lists","/dev/null"); + // do not store an mmap cache + Cnf.Set("Dir::Cache::pkgcache", ""); + Cnf.Set("Dir::Cache::srcpkgcache", ""); + // the protocols only propose actions, not do them + Cnf.Set("Debug::NoLocking", "true"); + Cnf.Set("APT::Get::Simulate", "true"); + + StatusFile.reset(nullptr); + return true; +} +bool edspSystem::Initialize(Configuration &Cnf) +{ + if (edspLikeSystem::Initialize(Cnf) == false) + return false; + std::string const tmp = GetTempDir(); + char tmpname[300]; + snprintf(tmpname, sizeof(tmpname), "%s/apt-edsp-solver-XXXXXX", tmp.c_str()); + if (nullptr == mkdtemp(tmpname)) + return false; + tempDir = tmpname; + tempStatesFile = flCombine(tempDir, "extended_states"); + Cnf.Set("Dir::State::extended_states", tempStatesFile); + tempPrefsFile = flCombine(tempDir, "apt_preferences"); + Cnf.Set("Dir::Etc::preferences", tempPrefsFile); + return true; +} + /*}}}*/ +// System::ArchiveSupported - Is a file format supported /*{{{*/ +bool edspLikeSystem::ArchiveSupported(const char * /*Type*/) +{ + return false; +} + /*}}}*/ +// System::Score - Never use the EDSP system automatically /*{{{*/ +signed edspLikeSystem::Score(Configuration const &) +{ + return -1000; +} + /*}}}*/ +// System::FindIndex - Get an index file for status files /*{{{*/ +bool edspLikeSystem::FindIndex(pkgCache::PkgFileIterator File, + pkgIndexFile *&Found) const +{ + if (StatusFile == 0) + return false; + if (StatusFile->FindInCache(*File.Cache()) == File) + { + Found = StatusFile.get(); + return true; + } + + return false; +} + /*}}}*/ +bool edspSystem::AddStatusFiles(std::vector<pkgIndexFile *> &List) /*{{{*/ +{ + if (StatusFile == nullptr) + { + if (_config->Find("edsp::scenario", "") == "/nonexistent/stdin") + StatusFile.reset(new edspIndex("/nonexistent/stdin")); + else + StatusFile.reset(new edspIndex(_config->FindFile("edsp::scenario"))); + } + List.push_back(StatusFile.get()); + return true; +} + /*}}}*/ +bool eippSystem::AddStatusFiles(std::vector<pkgIndexFile *> &List) /*{{{*/ +{ + if (StatusFile == nullptr) + { + if (_config->Find("eipp::scenario", "") == "/nonexistent/stdin") + StatusFile.reset(new eippIndex("/nonexistent/stdin")); + else + StatusFile.reset(new eippIndex(_config->FindFile("eipp::scenario"))); + } + List.push_back(StatusFile.get()); + return true; +} + /*}}}*/ + +edspLikeSystem::~edspLikeSystem() {} +edspSystem::~edspSystem() +{ + if (tempDir.empty()) + return; + + RemoveFile("~edspSystem", tempStatesFile); + RemoveFile("~edspSystem", tempPrefsFile); + rmdir(tempDir.c_str()); +} +eippSystem::~eippSystem() {} + +APT_HIDDEN edspSystem edspSys; +APT_HIDDEN eippSystem eippSys; diff --git a/apt-pkg/edsp/edspsystem.h b/apt-pkg/edsp/edspsystem.h new file mode 100644 index 0000000..0855bb5 --- /dev/null +++ b/apt-pkg/edsp/edspsystem.h @@ -0,0 +1,67 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + System - Debian version of the System Class + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_EDSPSYSTEM_H +#define PKGLIB_EDSPSYSTEM_H + +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgsystem.h> + +#include <memory> +#include <vector> + +#include <apt-pkg/macros.h> + +class Configuration; +class pkgDepCache; +class pkgIndexFile; +class pkgPackageManager; + +class APT_HIDDEN edspLikeSystem : public pkgSystem +{ +protected: + std::unique_ptr<pkgIndexFile> StatusFile; + +public: + virtual bool Lock() APT_OVERRIDE APT_PURE; + virtual bool UnLock(bool NoErrors = false) APT_OVERRIDE APT_PURE; + virtual pkgPackageManager *CreatePM(pkgDepCache *Cache) const APT_OVERRIDE APT_PURE; + virtual bool Initialize(Configuration &Cnf) APT_OVERRIDE; + virtual bool ArchiveSupported(const char *Type) APT_OVERRIDE APT_PURE; + virtual signed Score(Configuration const &Cnf) APT_OVERRIDE; + virtual bool FindIndex(pkgCache::PkgFileIterator File, + pkgIndexFile *&Found) const APT_OVERRIDE; + + edspLikeSystem(char const * const Label); + virtual ~edspLikeSystem(); +}; + +class APT_HIDDEN edspSystem : public edspLikeSystem +{ + std::string tempDir; + std::string tempStatesFile; + std::string tempPrefsFile; + +public: + virtual bool Initialize(Configuration &Cnf) APT_OVERRIDE; + virtual bool AddStatusFiles(std::vector<pkgIndexFile *> &List) APT_OVERRIDE; + + edspSystem(); + virtual ~edspSystem(); +}; + +class APT_HIDDEN eippSystem : public edspLikeSystem +{ + public: + virtual bool AddStatusFiles(std::vector<pkgIndexFile *> &List) APT_OVERRIDE; + + eippSystem(); + virtual ~eippSystem(); +}; + +#endif diff --git a/apt-pkg/indexcopy.cc b/apt-pkg/indexcopy.cc new file mode 100644 index 0000000..41cecde --- /dev/null +++ b/apt-pkg/indexcopy.cc @@ -0,0 +1,796 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Index Copying - Aid for copying and verifying the index files + + This class helps apt-cache reconstruct a damaged index files. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/cdrom.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/debmetaindex.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/gpgv.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/metaindex.h> +#include <apt-pkg/progress.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile.h> + +#include <iostream> +#include <sstream> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "indexcopy.h" +#include <apti18n.h> + /*}}}*/ + +using namespace std; + +// IndexCopy::CopyPackages - Copy the package files from the CD /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool IndexCopy::CopyPackages(string CDROM,string Name,vector<string> &List, + pkgCdromStatus *log) +{ + OpProgress *Progress = NULL; + if (List.empty() == true) + return true; + + if(log) + Progress = log->GetOpProgress(); + + bool NoStat = _config->FindB("APT::CDROM::Fast",false); + bool Debug = _config->FindB("Debug::aptcdrom",false); + + // Prepare the progress indicator + off_t TotalSize = 0; + std::vector<APT::Configuration::Compressor> const compressor = APT::Configuration::getCompressors(); + for (auto const &F : List) + { + struct stat Buf; + bool found = false; + auto const file = F + GetFileName(); + for (auto const &c : compressor) + { + if (stat((file + c.Extension).c_str(), &Buf) != 0) + continue; + found = true; + break; + } + + if (found == false) + return _error->Errno("stat", "Stat failed for %s", file.c_str()); + TotalSize += Buf.st_size; + } + + off_t CurrentSize = 0; + unsigned int NotFound = 0; + unsigned int WrongSize = 0; + unsigned int Packages = 0; + for (vector<string>::iterator I = List.begin(); I != List.end(); ++I) + { + std::string OrigPath(*I,CDROM.length()); + + // Open the package file + FileFd Pkg(*I + GetFileName(), FileFd::ReadOnly, FileFd::Auto); + off_t const FileSize = Pkg.Size(); + + pkgTagFile Parser(&Pkg); + if (Pkg.IsOpen() == false || Pkg.Failed()) + return false; + + // Open the output file + char S[400]; + snprintf(S,sizeof(S),"cdrom:[%s]/%s%s",Name.c_str(), + (*I).c_str() + CDROM.length(),GetFileName()); + string TargetF = _config->FindDir("Dir::State::lists") + "partial/"; + TargetF += URItoFileName(S); + FileFd Target; + if (_config->FindB("APT::CDROM::NoAct",false) == true) + { + TargetF = "/dev/null"; + Target.Open(TargetF,FileFd::WriteExists); + } else { + Target.Open(TargetF,FileFd::WriteAtomic); + } + if (Target.IsOpen() == false || Target.Failed()) + return false; + + // Setup the progress meter + if(Progress) + Progress->OverallProgress(CurrentSize,TotalSize,FileSize, + string("Reading ") + Type() + " Indexes"); + + // Parse + if(Progress) + Progress->SubProgress(Pkg.Size()); + pkgTagSection Section; + this->Section = &Section; + string Prefix; + unsigned long Hits = 0; + unsigned long Chop = 0; + while (Parser.Step(Section) == true) + { + if(Progress) + Progress->Progress(Parser.Offset()); + string File; + unsigned long long Size; + if (GetFile(File,Size) == false) + return false; + + if (Chop != 0) + File = OrigPath + ChopDirs(File,Chop); + + // See if the file exists + if (NoStat == false || Hits < 10) + { + // Attempt to fix broken structure + if (Hits == 0) + { + if (ReconstructPrefix(Prefix,OrigPath,CDROM,File) == false && + ReconstructChop(Chop,*I,File) == false) + { + if (Debug == true) + clog << "Missed: " << File << endl; + NotFound++; + continue; + } + if (Chop != 0) + File = OrigPath + ChopDirs(File,Chop); + } + + // Get the size + struct stat Buf; + if (stat((CDROM + Prefix + File).c_str(),&Buf) != 0 || + Buf.st_size == 0) + { + bool Mangled = false; + // Attempt to fix busted symlink support for one instance + string OrigFile = File; + string::size_type Start = File.find("binary-"); + string::size_type End = File.find("/",Start+3); + if (Start != string::npos && End != string::npos) + { + File.replace(Start,End-Start,"binary-all"); + Mangled = true; + } + + if (Mangled == false || + stat((CDROM + Prefix + File).c_str(),&Buf) != 0) + { + if (Debug == true) + clog << "Missed(2): " << OrigFile << endl; + NotFound++; + continue; + } + } + + // Size match + if ((unsigned long long)Buf.st_size != Size) + { + if (Debug == true) + clog << "Wrong Size: " << File << endl; + WrongSize++; + continue; + } + } + + Packages++; + Hits++; + + if (RewriteEntry(Target, File) == false) + return false; + } + + if (Debug == true) + cout << " Processed by using Prefix '" << Prefix << "' and chop " << Chop << endl; + + if (_config->FindB("APT::CDROM::NoAct",false) == false) + { + // Move out of the partial directory + Target.Close(); + string FinalF = _config->FindDir("Dir::State::lists"); + FinalF += URItoFileName(S); + if (rename(TargetF.c_str(),FinalF.c_str()) != 0) + return _error->Errno("rename","Failed to rename"); + ChangeOwnerAndPermissionOfFile("CopyPackages", FinalF.c_str(), "root", ROOT_GROUP, 0644); + } + + /* Mangle the source to be in the proper notation with + prefix dist [component] */ + *I = string(*I,Prefix.length()); + ConvertToSourceList(CDROM,*I); + *I = Prefix + ' ' + *I; + + CurrentSize += FileSize; + } + if(Progress) + Progress->Done(); + + // Some stats + if(log) { + stringstream msg; + if(NotFound == 0 && WrongSize == 0) + ioprintf(msg, _("Wrote %i records.\n"), Packages); + else if (NotFound != 0 && WrongSize == 0) + ioprintf(msg, _("Wrote %i records with %i missing files.\n"), + Packages, NotFound); + else if (NotFound == 0 && WrongSize != 0) + ioprintf(msg, _("Wrote %i records with %i mismatched files\n"), + Packages, WrongSize); + if (NotFound != 0 && WrongSize != 0) + ioprintf(msg, _("Wrote %i records with %i missing files and %i mismatched files\n"), Packages, NotFound, WrongSize); + } + + if (Packages == 0) + _error->Warning("No valid records were found."); + + if (NotFound + WrongSize > 10) + _error->Warning("A lot of entries were discarded, something may be wrong.\n"); + + return true; +} + /*}}}*/ +// IndexCopy::ChopDirs - Chop off the leading directory components /*{{{*/ +// --------------------------------------------------------------------- +/* */ +string IndexCopy::ChopDirs(string Path,unsigned int Depth) +{ + string::size_type I = 0; + do + { + I = Path.find('/',I+1); + Depth--; + } + while (I != string::npos && Depth != 0); + + if (I == string::npos) + return string(); + + return string(Path,I+1); +} + /*}}}*/ +// IndexCopy::ReconstructPrefix - Fix strange prefixing /*{{{*/ +// --------------------------------------------------------------------- +/* This prepends dir components from the path to the package files to + the path to the deb until it is found */ +bool IndexCopy::ReconstructPrefix(string &Prefix,string OrigPath,string CD, + string File) +{ + bool Debug = _config->FindB("Debug::aptcdrom",false); + unsigned int Depth = 1; + string MyPrefix = Prefix; + while (1) + { + struct stat Buf; + if (stat((CD + MyPrefix + File).c_str(),&Buf) != 0) + { + if (Debug == true) + cout << "Failed, " << CD + MyPrefix + File << endl; + if (GrabFirst(OrigPath,MyPrefix,Depth++) == true) + continue; + + return false; + } + else + { + Prefix = MyPrefix; + return true; + } + } + return false; +} + /*}}}*/ +// IndexCopy::ReconstructChop - Fixes bad source paths /*{{{*/ +// --------------------------------------------------------------------- +/* This removes path components from the filename and prepends the location + of the package files until a file is found */ +bool IndexCopy::ReconstructChop(unsigned long &Chop,string Dir,string File) +{ + // Attempt to reconstruct the filename + unsigned long Depth = 0; + while (1) + { + struct stat Buf; + if (stat((Dir + File).c_str(),&Buf) != 0) + { + File = ChopDirs(File,1); + Depth++; + if (File.empty() == false) + continue; + return false; + } + else + { + Chop = Depth; + return true; + } + } + return false; +} + /*}}}*/ +// IndexCopy::ConvertToSourceList - Convert a Path to a sourcelist /*{{{*/ +// --------------------------------------------------------------------- +/* We look for things in dists/ notation and convert them to + <dist> <component> form otherwise it is left alone. This also strips + the CD path. + + This implements a regex sort of like: + (.*)/dists/([^/]*)/(.*)/binary-* + ^ ^ ^- Component + | |-------- Distribution + |------------------- Path + + It was deciced to use only a single word for dist (rather than say + unstable/non-us) to increase the chance that each CD gets a single + line in sources.list. + */ +void IndexCopy::ConvertToSourceList(string CD,string &Path) +{ + // Strip the cdrom base path + Path = string(Path,CD.length()); + if (Path.empty() == true) + Path = "/"; + + // Too short to be a dists/ type + if (Path.length() < strlen("dists/")) + return; + + // Not a dists type. + if (stringcmp(Path.c_str(),Path.c_str()+strlen("dists/"),"dists/") != 0) + return; + + // Isolate the dist + string::size_type Slash = strlen("dists/"); + string::size_type Slash2 = Path.find('/',Slash + 1); + if (Slash2 == string::npos || Slash2 + 2 >= Path.length()) + return; + string Dist = string(Path,Slash,Slash2 - Slash); + + // Isolate the component + Slash = Slash2; + for (unsigned I = 0; I != 10; I++) + { + Slash = Path.find('/',Slash+1); + if (Slash == string::npos || Slash + 2 >= Path.length()) + return; + string Comp = string(Path,Slash2+1,Slash - Slash2-1); + + // Verify the trailing binary- bit + string::size_type BinSlash = Path.find('/',Slash + 1); + if (Slash == string::npos) + return; + string Binary = string(Path,Slash+1,BinSlash - Slash-1); + + if (strncmp(Binary.c_str(), "binary-", strlen("binary-")) == 0) + { + Binary.erase(0, strlen("binary-")); + if (APT::Configuration::checkArchitecture(Binary) == false) + continue; + } + else if (Binary != "source") + continue; + + Path = Dist + ' ' + Comp; + return; + } +} + /*}}}*/ +// IndexCopy::GrabFirst - Return the first Depth path components /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool IndexCopy::GrabFirst(string Path,string &To,unsigned int Depth) +{ + string::size_type I = 0; + do + { + I = Path.find('/',I+1); + Depth--; + } + while (I != string::npos && Depth != 0); + + if (I == string::npos) + return false; + + To = string(Path,0,I+1); + return true; +} + /*}}}*/ +// PackageCopy::GetFile - Get the file information from the section /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool PackageCopy::GetFile(string &File,unsigned long long &Size) +{ + File = Section->FindS("Filename"); + Size = Section->FindI("Size"); + if (File.empty() || Size == 0) + return _error->Error("Cannot find filename or size tag"); + return true; +} + /*}}}*/ +// PackageCopy::RewriteEntry - Rewrite the entry with a new filename /*{{{*/ +bool PackageCopy::RewriteEntry(FileFd &Target,string const &File) +{ + std::vector<pkgTagSection::Tag> Changes; + Changes.push_back(pkgTagSection::Tag::Rewrite("Filename", File)); + + if (Section->Write(Target, TFRewritePackageOrder, Changes) == false) + return false; + return Target.Write("\n", 1); +} + /*}}}*/ +// SourceCopy::GetFile - Get the file information from the section /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool SourceCopy::GetFile(string &File,unsigned long long &Size) +{ + string Files; + + for (char const *const *type = HashString::SupportedHashes(); *type != NULL; ++type) + { + // derive field from checksum type + std::string checksumField("Checksums-"); + if (strcmp(*type, "MD5Sum") == 0) + checksumField = "Files"; // historic name for MD5 checksums + else + checksumField.append(*type); + + Files = Section->FindS(checksumField.c_str()); + if (Files.empty() == false) + break; + } + if (Files.empty() == true) + return false; + + // Stash the / terminated directory prefix + string Base = Section->FindS("Directory"); + if (Base.empty() == false && Base[Base.length()-1] != '/') + Base += '/'; + + // Read the first file triplet + const char *C = Files.c_str(); + string sSize; + string MD5Hash; + + // Parse each of the elements + if (ParseQuoteWord(C,MD5Hash) == false || + ParseQuoteWord(C,sSize) == false || + ParseQuoteWord(C,File) == false) + return _error->Error("Error parsing file record"); + + // Parse the size and append the directory + Size = strtoull(sSize.c_str(), NULL, 10); + File = Base + File; + return true; +} + /*}}}*/ +// SourceCopy::RewriteEntry - Rewrite the entry with a new filename /*{{{*/ +bool SourceCopy::RewriteEntry(FileFd &Target, std::string const &File) +{ + string const Dir(File,0,File.rfind('/')); + std::vector<pkgTagSection::Tag> Changes; + Changes.push_back(pkgTagSection::Tag::Rewrite("Directory", Dir)); + + if (Section->Write(Target, TFRewriteSourceOrder, Changes) == false) + return false; + return Target.Write("\n", 1); +} + /*}}}*/ +// SigVerify::Verify - Verify a files md5sum against its metaindex /*{{{*/ +bool SigVerify::Verify(string prefix, string file, metaIndex *MetaIndex) +{ + const metaIndex::checkSum *Record = MetaIndex->Lookup(file); + bool const Debug = _config->FindB("Debug::aptcdrom",false); + + // we skip non-existing files in the verifcation of the Release file + // as non-existing files do not harm, but a warning scares people and + // makes it hard to strip unneeded files from an ISO like uncompressed + // indexes as it is done on the mirrors (see also LP: #255545 ) + if(!RealFileExists(prefix+file)) + { + if (Debug == true) + cout << "Skipping nonexistent in " << prefix << " file " << file << std::endl; + return true; + } + + if (!Record) + { + _error->Warning(_("Can't find authentication record for: %s"), file.c_str()); + return false; + } + + if (!Record->Hashes.VerifyFile(prefix+file)) + { + _error->Warning(_("Hash mismatch for: %s"),file.c_str()); + return false; + } + + if(Debug == true) + { + cout << "File: " << prefix+file << endl + << "Expected Hash " << endl; + for (HashStringList::const_iterator hs = Record->Hashes.begin(); hs != Record->Hashes.end(); ++hs) + std::cout << "\t- " << hs->toStr() << std::endl; + } + + return true; +} + /*}}}*/ +bool SigVerify::CopyMetaIndex(string CDROM, string CDName, /*{{{*/ + string prefix, string file) +{ + char S[400]; + snprintf(S,sizeof(S),"cdrom:[%s]/%s%s",CDName.c_str(), + (prefix).c_str() + CDROM.length(),file.c_str()); + string TargetF = _config->FindDir("Dir::State::lists"); + TargetF += URItoFileName(S); + + FileFd Target; + FileFd Rel; + Target.Open(TargetF,FileFd::WriteAtomic); + Rel.Open(prefix + file,FileFd::ReadOnly); + if (CopyFile(Rel,Target) == false || Target.Close() == false) + return _error->Error("Copying of '%s' for '%s' from '%s' failed", file.c_str(), CDName.c_str(), prefix.c_str()); + ChangeOwnerAndPermissionOfFile("CopyPackages", TargetF.c_str(), "root", ROOT_GROUP, 0644); + + return true; +} + /*}}}*/ +bool SigVerify::CopyAndVerify(string CDROM,string Name,vector<string> &SigList, /*{{{*/ + vector<string> /*PkgList*/,vector<string> /*SrcList*/) +{ + if (SigList.empty() == true) + return true; + + bool Debug = _config->FindB("Debug::aptcdrom",false); + + // Read all Release files + for (vector<string>::iterator I = SigList.begin(); I != SigList.end(); ++I) + { + if(Debug) + cout << "Signature verify for: " << *I << endl; + + metaIndex *MetaIndex = new debReleaseIndex("","", {}); + string prefix = *I; + + string const releasegpg = *I+"Release.gpg"; + string const release = *I+"Release"; + string const inrelease = *I+"InRelease"; + bool useInRelease = true; + + // a Release.gpg without a Release should never happen + if (RealFileExists(inrelease) == true) + ; + else if(RealFileExists(release) == false || RealFileExists(releasegpg) == false) + { + delete MetaIndex; + continue; + } + else + useInRelease = false; + + pid_t pid = ExecFork(); + if(pid < 0) { + _error->Error("Fork failed"); + return false; + } + if(pid == 0) + { + if (useInRelease == true) + ExecGPGV(inrelease, inrelease); + else + ExecGPGV(release, releasegpg); + } + + if(!ExecWait(pid, "gpgv")) { + _error->Warning("Signature verification failed for: %s", + (useInRelease ? inrelease.c_str() : releasegpg.c_str())); + // something went wrong, don't copy the Release.gpg + // FIXME: delete any existing gpg file? + delete MetaIndex; + continue; + } + + // Open the Release file and add it to the MetaIndex + std::string ErrorText; + if(MetaIndex->Load(release, &ErrorText) == false) + { + _error->Error("%s", ErrorText.c_str()); + return false; + } + + // go over the Indexfiles and see if they verify + // if so, remove them from our copy of the lists + vector<string> keys = MetaIndex->MetaKeys(); + for (vector<string>::iterator I = keys.begin(); I != keys.end(); ++I) + { + if(!Verify(prefix,*I, MetaIndex)) { + // something went wrong, don't copy the Release.gpg + // FIXME: delete any existing gpg file? + _error->Discard(); + continue; + } + } + + // we need a fresh one for the Release.gpg + delete MetaIndex; + + // everything was fine, copy the Release and Release.gpg file + if (useInRelease == true) + CopyMetaIndex(CDROM, Name, prefix, "InRelease"); + else + { + CopyMetaIndex(CDROM, Name, prefix, "Release"); + CopyMetaIndex(CDROM, Name, prefix, "Release.gpg"); + } + } + + return true; +} + /*}}}*/ +// SigVerify::RunGPGV - deprecated wrapper calling ExecGPGV /*{{{*/ +APT_NORETURN bool SigVerify::RunGPGV(std::string const &File, std::string const &FileOut, + int const &statusfd, int fd[2]) { + ExecGPGV(File, FileOut, statusfd, fd); +} +APT_NORETURN bool SigVerify::RunGPGV(std::string const &File, std::string const &FileOut, + int const &statusfd) { + ExecGPGV(File, FileOut, statusfd); +} + /*}}}*/ +bool TranslationsCopy::CopyTranslations(string CDROM,string Name, /*{{{*/ + vector<string> &List, pkgCdromStatus *log) +{ + OpProgress *Progress = NULL; + if (List.empty() == true) + return true; + + if(log) + Progress = log->GetOpProgress(); + + bool Debug = _config->FindB("Debug::aptcdrom",false); + + // Prepare the progress indicator + off_t TotalSize = 0; + std::vector<APT::Configuration::Compressor> const compressor = APT::Configuration::getCompressors(); + for (vector<string>::iterator I = List.begin(); I != List.end(); ++I) + { + struct stat Buf; + bool found = false; + std::string file = *I; + for (std::vector<APT::Configuration::Compressor>::const_iterator c = compressor.begin(); + c != compressor.end(); ++c) + { + if (stat((file + c->Extension).c_str(), &Buf) != 0) + continue; + found = true; + break; + } + + if (found == false) + return _error->Errno("stat", "Stat failed for %s", file.c_str()); + TotalSize += Buf.st_size; + } + + off_t CurrentSize = 0; + unsigned int NotFound = 0; + unsigned int WrongSize = 0; + unsigned int Packages = 0; + for (vector<string>::iterator I = List.begin(); I != List.end(); ++I) + { + // Open the package file + FileFd Pkg(*I, FileFd::ReadOnly, FileFd::Auto); + off_t const FileSize = Pkg.Size(); + + pkgTagFile Parser(&Pkg); + if (Pkg.IsOpen() == false || Pkg.Failed()) + return false; + + // Open the output file + char S[400]; + snprintf(S,sizeof(S),"cdrom:[%s]/%s",Name.c_str(), + (*I).c_str() + CDROM.length()); + string TargetF = _config->FindDir("Dir::State::lists") + "partial/"; + TargetF += URItoFileName(S); + FileFd Target; + if (_config->FindB("APT::CDROM::NoAct",false) == true) + { + TargetF = "/dev/null"; + Target.Open(TargetF,FileFd::WriteExists); + } else { + Target.Open(TargetF,FileFd::WriteAtomic); + } + if (Pkg.IsOpen() == false || Pkg.Failed()) + return false; + + // Setup the progress meter + if(Progress) + Progress->OverallProgress(CurrentSize,TotalSize,FileSize, + string("Reading Translation Indexes")); + + // Parse + if(Progress) + Progress->SubProgress(Pkg.Size()); + pkgTagSection Section; + this->Section = &Section; + string Prefix; + unsigned long Hits = 0; + while (Parser.Step(Section) == true) + { + if(Progress) + Progress->Progress(Parser.Offset()); + + if (Section.Write(Target) == false || Target.Write("\n", 1) == false) + return false; + + Packages++; + Hits++; + } + + if (Debug == true) + cout << " Processed by using Prefix '" << Prefix << "' and chop " << endl; + + if (_config->FindB("APT::CDROM::NoAct",false) == false) + { + // Move out of the partial directory + Target.Close(); + string FinalF = _config->FindDir("Dir::State::lists"); + FinalF += URItoFileName(S); + if (rename(TargetF.c_str(),FinalF.c_str()) != 0) + return _error->Errno("rename","Failed to rename"); + ChangeOwnerAndPermissionOfFile("CopyTranslations", FinalF.c_str(), "root", ROOT_GROUP, 0644); + } + + CurrentSize += FileSize; + } + if(Progress) + Progress->Done(); + + // Some stats + if(log) { + stringstream msg; + if(NotFound == 0 && WrongSize == 0) + ioprintf(msg, _("Wrote %i records.\n"), Packages); + else if (NotFound != 0 && WrongSize == 0) + ioprintf(msg, _("Wrote %i records with %i missing files.\n"), + Packages, NotFound); + else if (NotFound == 0 && WrongSize != 0) + ioprintf(msg, _("Wrote %i records with %i mismatched files\n"), + Packages, WrongSize); + if (NotFound != 0 && WrongSize != 0) + ioprintf(msg, _("Wrote %i records with %i missing files and %i mismatched files\n"), Packages, NotFound, WrongSize); + } + + if (Packages == 0) + _error->Warning("No valid records were found."); + + if (NotFound + WrongSize > 10) + _error->Warning("A lot of entries were discarded, something may be wrong.\n"); + + return true; +} + /*}}}*/ + +IndexCopy::IndexCopy() : d(nullptr), Section(nullptr) {} +IndexCopy::~IndexCopy() {} + +PackageCopy::PackageCopy() : IndexCopy(), d(NULL) {} +PackageCopy::~PackageCopy() {} +SourceCopy::SourceCopy() : IndexCopy(), d(NULL) {} +SourceCopy::~SourceCopy() {} +TranslationsCopy::TranslationsCopy() : d(nullptr), Section(nullptr) {} +TranslationsCopy::~TranslationsCopy() {} +SigVerify::SigVerify() : d(NULL) {} +SigVerify::~SigVerify() {} diff --git a/apt-pkg/indexcopy.h b/apt-pkg/indexcopy.h new file mode 100644 index 0000000..dc3b985 --- /dev/null +++ b/apt-pkg/indexcopy.h @@ -0,0 +1,129 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Index Copying - Aid for copying and verifying the index files + + ##################################################################### */ + /*}}}*/ +#ifndef INDEXCOPY_H +#define INDEXCOPY_H + +#include <vector> +#ifndef APT_11_CLEAN_HEADERS +#include <string> +#include <stdio.h> +#endif + +#include <apt-pkg/macros.h> + +#ifndef APT_10_CLEANER_HEADERS +#include <apt-pkg/gpgv.h> +class FileFd; +#endif +#ifndef APT_8_CLEANER_HEADERS +using std::string; +using std::vector; +#endif + +class pkgTagSection; +class pkgCdromStatus; +class FileFd; +class metaIndex; + +class IndexCopy /*{{{*/ +{ + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + protected: + + pkgTagSection *Section; + + std::string ChopDirs(std::string Path,unsigned int Depth); + bool ReconstructPrefix(std::string &Prefix,std::string OrigPath,std::string CD, + std::string File); + bool ReconstructChop(unsigned long &Chop,std::string Dir,std::string File); + void ConvertToSourceList(std::string CD,std::string &Path); + bool GrabFirst(std::string Path,std::string &To,unsigned int Depth); + virtual bool GetFile(std::string &Filename,unsigned long long &Size) = 0; + virtual bool RewriteEntry(FileFd &Target, std::string const &File) = 0; + virtual const char *GetFileName() = 0; + virtual const char *Type() = 0; + + public: + + bool CopyPackages(std::string CDROM,std::string Name,std::vector<std::string> &List, + pkgCdromStatus *log); + IndexCopy(); + virtual ~IndexCopy(); +}; + /*}}}*/ +class PackageCopy : public IndexCopy /*{{{*/ +{ + void * const d; + protected: + + virtual bool GetFile(std::string &Filename,unsigned long long &Size) APT_OVERRIDE; + virtual bool RewriteEntry(FileFd &Target, std::string const &File) APT_OVERRIDE; + virtual const char *GetFileName() APT_OVERRIDE {return "Packages";}; + virtual const char *Type() APT_OVERRIDE {return "Package";}; + + public: + PackageCopy(); + virtual ~PackageCopy(); +}; + /*}}}*/ +class SourceCopy : public IndexCopy /*{{{*/ +{ + void * const d; + protected: + + virtual bool GetFile(std::string &Filename,unsigned long long &Size) APT_OVERRIDE; + virtual bool RewriteEntry(FileFd &Target, std::string const &File) APT_OVERRIDE; + virtual const char *GetFileName() APT_OVERRIDE {return "Sources";}; + virtual const char *Type() APT_OVERRIDE {return "Source";}; + + public: + SourceCopy(); + virtual ~SourceCopy(); +}; + /*}}}*/ +class TranslationsCopy /*{{{*/ +{ + void * const d; + protected: + pkgTagSection *Section; + + public: + bool CopyTranslations(std::string CDROM,std::string Name,std::vector<std::string> &List, + pkgCdromStatus *log); + + TranslationsCopy(); + virtual ~TranslationsCopy(); +}; + /*}}}*/ +class SigVerify /*{{{*/ +{ + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + APT_HIDDEN bool Verify(std::string prefix,std::string file, metaIndex *records); + APT_HIDDEN bool CopyMetaIndex(std::string CDROM, std::string CDName, + std::string prefix, std::string file); + + public: + bool CopyAndVerify(std::string CDROM,std::string Name,std::vector<std::string> &SigList, + std::vector<std::string> PkgList,std::vector<std::string> SrcList); + + APT_DEPRECATED_MSG("Use ExecGPGV instead") static bool RunGPGV(std::string const &File, std::string const &FileOut, + int const &statusfd, int fd[2]); + APT_DEPRECATED_MSG("Use ExecGPGV instead") static bool RunGPGV(std::string const &File, std::string const &FileOut, + int const &statusfd = -1); + + SigVerify(); + virtual ~SigVerify(); +}; + /*}}}*/ + +#endif diff --git a/apt-pkg/indexfile.cc b/apt-pkg/indexfile.cc new file mode 100644 index 0000000..85d0e87 --- /dev/null +++ b/apt-pkg/indexfile.cc @@ -0,0 +1,412 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Index File - Abstraction for an index of archive/source file. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/deblistparser.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgcachegen.h> +#include <apt-pkg/progress.h> +#include <apt-pkg/srcrecords.h> +#include <apt-pkg/strutl.h> + +#include <apt-pkg/debindexfile.h> + +#include <sys/stat.h> + +#include <clocale> +#include <cstring> +#include <memory> +#include <string> +#include <vector> + /*}}}*/ + +// Global list of Item supported +static pkgIndexFile::Type *ItmList[10]; +pkgIndexFile::Type **pkgIndexFile::Type::GlobalList = ItmList; +unsigned long pkgIndexFile::Type::GlobalListLen = 0; + +// Type::Type - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgIndexFile::Type::Type() +{ + ItmList[GlobalListLen] = this; + GlobalListLen++; + Label = NULL; +} + /*}}}*/ +// Type::GetType - Locate the type by name /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgIndexFile::Type *pkgIndexFile::Type::GetType(const char *Type) +{ + for (unsigned I = 0; I != GlobalListLen; I++) + if (strcmp(GlobalList[I]->Label,Type) == 0) + return GlobalList[I]; + return 0; +} + /*}}}*/ +pkgIndexFile::pkgIndexFile(bool const Trusted) : /*{{{*/ + d(NULL), Trusted(Trusted) +{ +} + /*}}}*/ +// IndexFile::ArchiveInfo - Stub /*{{{*/ +std::string pkgIndexFile::ArchiveInfo(pkgCache::VerIterator const &Ver) const +{ + debDebPkgFileIndex const * const debfile = dynamic_cast<debDebPkgFileIndex const*>(this); + if (debfile != nullptr) + return debfile->ArchiveInfo_impl(Ver); + return std::string(); +} + /*}}}*/ +// IndexFile::FindInCache - Stub /*{{{*/ +pkgCache::PkgFileIterator pkgIndexFile::FindInCache(pkgCache &Cache) const +{ + return pkgCache::PkgFileIterator(Cache); +} + /*}}}*/ +// IndexFile::SourceIndex - Stub /*{{{*/ +std::string pkgIndexFile::SourceInfo(pkgSrcRecords::Parser const &/*Record*/, + pkgSrcRecords::File const &/*File*/) const +{ + return std::string(); +} + /*}}}*/ +// IndexFile::TranslationsAvailable - Check if will use Translation /*{{{*/ +bool pkgIndexFile::TranslationsAvailable() { + return (APT::Configuration::getLanguages().empty() != true); +} + /*}}}*/ +// IndexFile::CheckLanguageCode - Check the Language Code /*{{{*/ +bool pkgIndexFile::CheckLanguageCode(const char * const Lang) +{ + if (strlen(Lang) == 2 || (strlen(Lang) == 5 && Lang[2] == '_')) + return true; + + if (strcmp(Lang,"C") != 0) + _error->Warning("Wrong language code %s", Lang); + + return false; +} + /*}}}*/ +// IndexFile::LanguageCode - Return the Language Code /*{{{*/ +std::string pkgIndexFile::LanguageCode() { +APT_IGNORE_DEPRECATED_PUSH + if (TranslationsAvailable() == false) + return ""; + return APT::Configuration::getLanguages()[0]; +APT_IGNORE_DEPRECATED_POP +} + /*}}}*/ + +// IndexTarget - Constructor /*{{{*/ +IndexTarget::IndexTarget(std::string const &MetaKey, std::string const &ShortDesc, + std::string const &LongDesc, std::string const &URI, bool const IsOptional, + bool const KeepCompressed, std::map<std::string, std::string> const &Options) : + URI(URI), Description(LongDesc), ShortDesc(ShortDesc), MetaKey(MetaKey), + IsOptional(IsOptional), KeepCompressed(KeepCompressed), Options(Options) +{ +} + /*}}}*/ +std::string IndexTarget::Option(OptionKeys const EnumKey) const /*{{{*/ +{ + std::string Key; + switch (EnumKey) + { +#define APT_CASE(X) case X: Key = #X; break + APT_CASE(SITE); + APT_CASE(RELEASE); + APT_CASE(COMPONENT); + APT_CASE(LANGUAGE); + APT_CASE(ARCHITECTURE); + APT_CASE(BASE_URI); + APT_CASE(REPO_URI); + APT_CASE(IDENTIFIER); + APT_CASE(TARGET_OF); + APT_CASE(CREATED_BY); + APT_CASE(FALLBACK_OF); + APT_CASE(PDIFFS); + APT_CASE(DEFAULTENABLED); + APT_CASE(COMPRESSIONTYPES); + APT_CASE(SOURCESENTRY); + APT_CASE(BY_HASH); + APT_CASE(KEEPCOMPRESSEDAS); + APT_CASE(ALLOW_INSECURE); + APT_CASE(ALLOW_WEAK); + APT_CASE(ALLOW_DOWNGRADE_TO_INSECURE); + APT_CASE(INRELEASE_PATH); +#undef APT_CASE + case FILENAME: + { + auto const M = Options.find("FILENAME"); + if (M == Options.end()) + return _config->FindDir("Dir::State::lists") + URItoFileName(URI); + return M->second; + } + case EXISTING_FILENAME: + std::string const filename = Option(FILENAME); + std::vector<std::string> const types = VectorizeString(Option(COMPRESSIONTYPES), ' '); + for (std::vector<std::string>::const_iterator t = types.begin(); t != types.end(); ++t) + { + if (t->empty()) + continue; + std::string const file = (*t == "uncompressed") ? filename : (filename + "." + *t); + if (FileExists(file)) + return file; + } + return ""; + } + std::map<std::string,std::string>::const_iterator const M = Options.find(Key); + if (M == Options.end()) + return ""; + return M->second; +} + /*}}}*/ +bool IndexTarget::OptionBool(OptionKeys const EnumKey) const /*{{{*/ +{ + return StringToBool(Option(EnumKey), false); +} + /*}}}*/ +std::string IndexTarget::Format(std::string format) const /*{{{*/ +{ + for (std::map<std::string, std::string>::const_iterator O = Options.begin(); O != Options.end(); ++O) + { + format = SubstVar(format, std::string("$(") + O->first + ")", O->second); + } + format = SubstVar(format, "$(METAKEY)", MetaKey); + format = SubstVar(format, "$(SHORTDESC)", ShortDesc); + format = SubstVar(format, "$(DESCRIPTION)", Description); + format = SubstVar(format, "$(URI)", URI); + format = SubstVar(format, "$(FILENAME)", Option(IndexTarget::FILENAME)); + return format; +} + /*}}}*/ + +pkgDebianIndexTargetFile::pkgDebianIndexTargetFile(IndexTarget const &Target, bool const Trusted) :/*{{{*/ + pkgDebianIndexFile(Trusted), d(NULL), Target(Target) +{ +} + /*}}}*/ +std::string pkgDebianIndexTargetFile::ArchiveURI(std::string const &File) const/*{{{*/ +{ + return Target.Option(IndexTarget::REPO_URI) + File; +} + /*}}}*/ +std::string pkgDebianIndexTargetFile::Describe(bool const Short) const /*{{{*/ +{ + if (Short) + return Target.Description; + return Target.Description + " (" + IndexFileName() + ")"; +} + /*}}}*/ +std::string pkgDebianIndexTargetFile::IndexFileName() const /*{{{*/ +{ + std::string const s = Target.Option(IndexTarget::FILENAME); + if (FileExists(s)) + return s; + + std::vector<std::string> const types = VectorizeString(Target.Option(IndexTarget::COMPRESSIONTYPES), ' '); + for (std::vector<std::string>::const_iterator t = types.begin(); t != types.end(); ++t) + { + std::string p = s + '.' + *t; + if (FileExists(p)) + return p; + } + return s; +} + /*}}}*/ +unsigned long pkgDebianIndexTargetFile::Size() const /*{{{*/ +{ + unsigned long size = 0; + + /* we need to ignore errors here; if the lists are absent, just return 0 */ + _error->PushToStack(); + + FileFd f(IndexFileName(), FileFd::ReadOnly, FileFd::Extension); + if (!f.Failed()) + size = f.Size(); + + if (_error->PendingError() == true) + size = 0; + _error->RevertToStack(); + + return size; +} + /*}}}*/ +bool pkgDebianIndexTargetFile::Exists() const /*{{{*/ +{ + return FileExists(IndexFileName()); +} + /*}}}*/ +std::string pkgDebianIndexTargetFile::GetArchitecture() const /*{{{*/ +{ + return Target.Option(IndexTarget::ARCHITECTURE); +} + /*}}}*/ +std::string pkgDebianIndexTargetFile::GetComponent() const /*{{{*/ +{ + return Target.Option(IndexTarget::COMPONENT); +} + /*}}}*/ +bool pkgDebianIndexTargetFile::OpenListFile(FileFd &Pkg, std::string const &FileName)/*{{{*/ +{ + if (Pkg.Open(FileName, FileFd::ReadOnly, FileFd::Extension) == false) + return _error->Error("Problem opening %s",FileName.c_str()); + return true; +} + /*}}}*/ +std::string pkgDebianIndexTargetFile::GetProgressDescription() const +{ + return Target.Description; +} +IndexTarget pkgDebianIndexTargetFile::GetIndexTarget() const +{ + return Target; +} + +pkgDebianIndexRealFile::pkgDebianIndexRealFile(std::string const &pFile, bool const Trusted) :/*{{{*/ + pkgDebianIndexFile(Trusted), d(NULL) +{ + if (pFile.empty()) + ; + else if (pFile == "/nonexistent/stdin") + File = pFile; + else + File = flAbsPath(pFile); +} + /*}}}*/ +// IndexRealFile::Size - Return the size of the index /*{{{*/ +unsigned long pkgDebianIndexRealFile::Size() const +{ + struct stat S; + if (stat(File.c_str(),&S) != 0) + return 0; + return S.st_size; +} + /*}}}*/ +bool pkgDebianIndexRealFile::Exists() const /*{{{*/ +{ + return FileExists(File); +} + /*}}}*/ +std::string pkgDebianIndexRealFile::Describe(bool const /*Short*/) const/*{{{*/ +{ + return File; +} + /*}}}*/ +std::string pkgDebianIndexRealFile::ArchiveURI(std::string const &/*File*/) const/*{{{*/ +{ + return "file:" + File; +} + /*}}}*/ +std::string pkgDebianIndexRealFile::IndexFileName() const /*{{{*/ +{ + return File; +} + /*}}}*/ +std::string pkgDebianIndexRealFile::GetProgressDescription() const +{ + return File; +} +bool pkgDebianIndexRealFile::OpenListFile(FileFd &Pkg, std::string const &FileName)/*{{{*/ +{ + if (Pkg.Open(FileName, FileFd::ReadOnly, FileFd::Extension) == false) + return _error->Error("Problem opening %s",FileName.c_str()); + return true; +} + /*}}}*/ + +pkgDebianIndexFile::pkgDebianIndexFile(bool const Trusted) : pkgIndexFile(Trusted) +{ +} +pkgDebianIndexFile::~pkgDebianIndexFile() +{ +} +pkgCacheListParser * pkgDebianIndexFile::CreateListParser(FileFd &Pkg) +{ + if (Pkg.IsOpen() == false) + return nullptr; + _error->PushToStack(); + std::unique_ptr<pkgCacheListParser> Parser(new debListParser(&Pkg)); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + return newError ? nullptr : Parser.release(); +} +bool pkgDebianIndexFile::Merge(pkgCacheGenerator &Gen,OpProgress * const Prog) +{ + std::string const PackageFile = IndexFileName(); + FileFd Pkg; + if (OpenListFile(Pkg, PackageFile) == false) + return false; + _error->PushToStack(); + std::unique_ptr<pkgCacheListParser> Parser(CreateListParser(Pkg)); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + if (newError == false && Parser == nullptr) + return true; + if (Parser == NULL) + return false; + + if (Prog != NULL) + Prog->SubProgress(0, GetProgressDescription()); + + if (Gen.SelectFile(PackageFile, *this, GetArchitecture(), GetComponent(), GetIndexFlags()) == false) + return _error->Error("Problem with SelectFile %s",PackageFile.c_str()); + + // Store the IMS information + pkgCache::PkgFileIterator File = Gen.GetCurFile(); + pkgCacheGenerator::Dynamic<pkgCache::PkgFileIterator> DynFile(File); + File->Size = Pkg.FileSize(); + File->mtime = Pkg.ModificationTime(); + + if (Gen.MergeList(*Parser) == false) + return _error->Error("Problem with MergeList %s",PackageFile.c_str()); + return true; +} +pkgCache::PkgFileIterator pkgDebianIndexFile::FindInCache(pkgCache &Cache) const +{ + std::string const FileName = IndexFileName(); + pkgCache::PkgFileIterator File = Cache.FileBegin(); + for (; File.end() == false; ++File) + { + if (File.FileName() == NULL || FileName != File.FileName()) + continue; + + struct stat St; + if (stat(File.FileName(),&St) != 0) + { + if (_config->FindB("Debug::pkgCacheGen", false)) + std::clog << "DebianIndexFile::FindInCache - stat failed on " << File.FileName() << std::endl; + return pkgCache::PkgFileIterator(Cache); + } + if ((map_filesize_t)St.st_size != File->Size || St.st_mtime != File->mtime) + { + if (_config->FindB("Debug::pkgCacheGen", false)) + std::clog << "DebianIndexFile::FindInCache - size (" << St.st_size << " <> " << File->Size + << ") or mtime (" << St.st_mtime << " <> " << File->mtime + << ") doesn't match for " << File.FileName() << std::endl; + return pkgCache::PkgFileIterator(Cache); + } + return File; + } + + return File; +} + +pkgIndexFile::~pkgIndexFile() {} +pkgDebianIndexTargetFile::~pkgDebianIndexTargetFile() {} +pkgDebianIndexRealFile::~pkgDebianIndexRealFile() {} diff --git a/apt-pkg/indexfile.h b/apt-pkg/indexfile.h new file mode 100644 index 0000000..31b9517 --- /dev/null +++ b/apt-pkg/indexfile.h @@ -0,0 +1,229 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Index File - Abstraction for an index of archive/source file. + + There are 4 primary sorts of index files, all represented by this + class: + + Binary index files + Binary translation files + Binary index files describing the local system + Source index files + + They are all bundled together here, and the interfaces for + sources.list, acquire, cache gen and record parsing all use this class + to access the underlying representation. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_INDEXFILE_H +#define PKGLIB_INDEXFILE_H + +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/srcrecords.h> + +#include <map> +#include <string> + +#ifndef APT_8_CLEANER_HEADERS +using std::string; +#endif +#ifndef APT_10_CLEANER_HEADERS +class pkgAcquire; +#endif + +class pkgCacheGenerator; +class pkgCacheListParser; +class OpProgress; + +class IndexTarget /*{{{*/ +/** \brief Information about an index file. */ +{ + public: + /** \brief A URI from which the index file can be downloaded. */ + std::string URI; + + /** \brief A description of the index file. */ + std::string Description; + + /** \brief A shorter description of the index file. */ + std::string ShortDesc; + + /** \brief The key by which this index file should be + looked up within the meta index file. */ + std::string MetaKey; + + /** \brief Is it okay if the file isn't found in the meta index */ + bool IsOptional; + + /** \brief If the file is downloaded compressed, do not unpack it */ + bool KeepCompressed; + + /** \brief options with which this target was created + Prefer the usage of #Option if at all possible. + Beware: Not all of these options are intended for public use */ + std::map<std::string, std::string> Options; + + IndexTarget(std::string const &MetaKey, std::string const &ShortDesc, + std::string const &LongDesc, std::string const &URI, bool const IsOptional, + bool const KeepCompressed, std::map<std::string, std::string> const &Options); + + enum OptionKeys + { + SITE, + RELEASE, + COMPONENT, + LANGUAGE, + ARCHITECTURE, + BASE_URI, + REPO_URI, + CREATED_BY, + TARGET_OF, + FILENAME, + EXISTING_FILENAME, + PDIFFS, + COMPRESSIONTYPES, + DEFAULTENABLED, + SOURCESENTRY, + BY_HASH, + KEEPCOMPRESSEDAS, + FALLBACK_OF, + IDENTIFIER, + ALLOW_INSECURE, + ALLOW_WEAK, + ALLOW_DOWNGRADE_TO_INSECURE, + INRELEASE_PATH, + }; + std::string Option(OptionKeys const Key) const; + bool OptionBool(OptionKeys const Key) const; + std::string Format(std::string format) const; +}; + /*}}}*/ + +class pkgIndexFile +{ + void * const d; + protected: + bool Trusted; + + public: + + class Type + { + public: + + // Global list of Items supported + static Type **GlobalList; + static unsigned long GlobalListLen; + static Type *GetType(const char * const Type) APT_PURE; + + const char *Label; + + virtual pkgRecords::Parser *CreatePkgParser(pkgCache::PkgFileIterator const &/*File*/) const {return 0;}; + virtual pkgSrcRecords::Parser *CreateSrcPkgParser(std::string const &/*File*/) const {return 0;}; + Type(); + virtual ~Type() {}; + }; + + virtual const Type *GetType() const = 0; + + // Return descriptive strings of various sorts + virtual std::string ArchiveInfo(pkgCache::VerIterator const &Ver) const; + virtual std::string SourceInfo(pkgSrcRecords::Parser const &Record, + pkgSrcRecords::File const &File) const; + virtual std::string Describe(bool const Short = false) const = 0; + + // Interface for acquire + virtual std::string ArchiveURI(std::string const &/*File*/) const {return std::string();}; + + // Interface for the record parsers + virtual pkgSrcRecords::Parser *CreateSrcParser() const {return 0;}; + + // Interface for the Cache Generator + virtual bool Exists() const = 0; + virtual bool HasPackages() const = 0; + virtual unsigned long Size() const = 0; + virtual bool Merge(pkgCacheGenerator &/*Gen*/, OpProgress* const /*Prog*/) { return true; }; + virtual pkgCache::PkgFileIterator FindInCache(pkgCache &Cache) const; + + APT_DEPRECATED_MSG("These methods make no sense anymore with multi-language support") static bool TranslationsAvailable(); + /* No intern need for this method anymore as the check for correctness + is already done in getLanguages(). Note also that this check is + rather bad (doesn't take three character like ast into account).*/ + APT_DEPRECATED_MSG("These methods make no sense anymore with multi-language support") static bool CheckLanguageCode(const char * const Lang); + /* As we have now possibly more than one LanguageCode this method is + superseded by a) private classmembers or b) getLanguages() */ + APT_DEPRECATED_MSG("These methods make no sense anymore with multi-language support") static std::string LanguageCode(); + + bool IsTrusted() const { return Trusted; }; + + explicit pkgIndexFile(bool const Trusted); + virtual ~pkgIndexFile(); +}; + +class pkgDebianIndexFile : public pkgIndexFile +{ +protected: + virtual std::string IndexFileName() const = 0; + virtual std::string GetComponent() const = 0; + virtual std::string GetArchitecture() const = 0; + virtual std::string GetProgressDescription() const = 0; + virtual uint8_t GetIndexFlags() const = 0; + virtual bool OpenListFile(FileFd &Pkg, std::string const &FileName) = 0; + APT_HIDDEN virtual pkgCacheListParser * CreateListParser(FileFd &Pkg); + +public: + virtual bool Merge(pkgCacheGenerator &Gen, OpProgress* const Prog) APT_OVERRIDE; + virtual pkgCache::PkgFileIterator FindInCache(pkgCache &Cache) const APT_OVERRIDE; + + explicit pkgDebianIndexFile(bool const Trusted); + virtual ~pkgDebianIndexFile(); +}; + +class pkgDebianIndexTargetFile : public pkgDebianIndexFile +{ + void * const d; +protected: + IndexTarget const Target; + + virtual std::string IndexFileName() const APT_OVERRIDE; + virtual std::string GetComponent() const APT_OVERRIDE; + virtual std::string GetArchitecture() const APT_OVERRIDE; + virtual std::string GetProgressDescription() const APT_OVERRIDE; + virtual bool OpenListFile(FileFd &Pkg, std::string const &FileName) APT_OVERRIDE; + +public: + virtual std::string ArchiveURI(std::string const &File) const APT_OVERRIDE; + virtual std::string Describe(bool const Short = false) const APT_OVERRIDE; + virtual bool Exists() const APT_OVERRIDE; + virtual unsigned long Size() const APT_OVERRIDE; + IndexTarget GetIndexTarget() const APT_HIDDEN; + + pkgDebianIndexTargetFile(IndexTarget const &Target, bool const Trusted); + virtual ~pkgDebianIndexTargetFile(); +}; + +class pkgDebianIndexRealFile : public pkgDebianIndexFile +{ + void * const d; +protected: + std::string File; + + virtual std::string IndexFileName() const APT_OVERRIDE; + virtual std::string GetProgressDescription() const APT_OVERRIDE; + virtual bool OpenListFile(FileFd &Pkg, std::string const &FileName) APT_OVERRIDE; +public: + virtual std::string Describe(bool const /*Short*/ = false) const APT_OVERRIDE; + virtual bool Exists() const APT_OVERRIDE; + virtual unsigned long Size() const APT_OVERRIDE; + virtual std::string ArchiveURI(std::string const &/*File*/) const APT_OVERRIDE; + + pkgDebianIndexRealFile(std::string const &File, bool const Trusted); + virtual ~pkgDebianIndexRealFile(); +}; + +#endif diff --git a/apt-pkg/init.cc b/apt-pkg/init.cc new file mode 100644 index 0000000..a619368 --- /dev/null +++ b/apt-pkg/init.cc @@ -0,0 +1,289 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Init - Initialize the package library + + ##################################################################### */ + /*}}}*/ +// Include files /*{{{*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/init.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgsystem.h> +#include <apt-pkg/strutl.h> + +#include <cstdlib> +#include <fstream> +#include <sstream> +#include <string> +#include <unordered_map> +#include <vector> +#include <string.h> + +#include <apti18n.h> + /*}}}*/ + +#define Stringfy_(x) # x +#define Stringfy(x) Stringfy_(x) +const char *pkgVersion = PACKAGE_VERSION; +const char *pkgLibVersion = Stringfy(APT_PKG_MAJOR) "." + Stringfy(APT_PKG_MINOR) "." + Stringfy(APT_PKG_RELEASE); +namespace APT { + APT_HIDDEN extern std::unordered_map<std::string, std::vector<std::string>> ArchToTupleMap; +} + +// Splits by whitespace. There may be continuous spans of whitespace - they +// will be considered as one. +static std::vector<std::string> split(std::string const & s) +{ + std::vector<std::string> vec; + std::istringstream iss(s); + iss.imbue(std::locale::classic()); + for(std::string current_s; iss >> current_s; ) + vec.push_back(current_s); + return vec; +} + + +// pkgInitArchTupleMap - Initialize the architecture tuple map /*{{{*/ +// --------------------------------------------------------------------- +/* This initializes */ +static bool pkgInitArchTupleMap() +{ + auto tuplepath = _config->FindFile("Dir::dpkg::tupletable", DPKG_DATADIR "/tupletable"); + auto tripletpath = _config->FindFile("Dir::dpkg::triplettable", DPKG_DATADIR "/triplettable"); + auto cpupath = _config->FindFile("Dir::dpkg::cputable", DPKG_DATADIR "/cputable"); + + // Load a list of CPUs + std::vector<std::string> cpus; + std::ifstream cputable(cpupath); + for (std::string cpuline; std::getline(cputable, cpuline); ) + { + if (cpuline[0] == '#' || cpuline[0] == '\0') + continue; + auto cpurow = split(cpuline); + auto cpu = APT::String::Strip(cpurow.at(0)); + + cpus.push_back(cpu); + } + if (!cputable.eof()) + return _error->Error("Error reading the CPU table"); + + // Load the architecture tuple + std::ifstream tupletable; + if (FileExists(tuplepath)) + tupletable.open(tuplepath); + else if (FileExists(tripletpath)) + tupletable.open(tripletpath); + else + return _error->Error("Cannot find dpkg tuplet or triplet table"); + + APT::ArchToTupleMap.clear(); + for (std::string tupleline; std::getline(tupletable, tupleline); ) + { + if (tupleline[0] == '#' || tupleline[0] == '\0') + continue; + + std::vector<std::string> tuplerow = split(tupleline); + + auto tuple = APT::String::Strip(tuplerow.at(0)); + auto arch = APT::String::Strip(tuplerow.at(1)); + + if (tuple.find("<cpu>") == tuple.npos && arch.find("<cpu>") == arch.npos) + { + APT::ArchToTupleMap.insert({arch, VectorizeString(tuple, '-')}); + } + else + { + for (auto && cpu : cpus) + { + auto mytuple = SubstVar(tuple, std::string("<cpu>"), cpu); + auto myarch = SubstVar(arch, std::string("<cpu>"), cpu); + + APT::ArchToTupleMap.insert({myarch, VectorizeString(mytuple, '-')}); + } + } + } + if (!tupletable.eof()) + return _error->Error("Error reading the Tuple table"); + + return true; +} + /*}}}*/ + + +// pkgInitConfig - Initialize the configuration class /*{{{*/ +// --------------------------------------------------------------------- +/* Directories are specified in such a way that the FindDir function will + understand them. That is, if they don't start with a / then their parent + is prepended, this allows a fair degree of flexibility. */ +bool pkgInitConfig(Configuration &Cnf) +{ + // General APT things + Cnf.CndSet("APT::Architecture", COMMON_ARCH); + if (Cnf.Exists("APT::Build-Essential") == false) + Cnf.Set("APT::Build-Essential::", "build-essential"); + Cnf.CndSet("APT::Install-Recommends", true); + Cnf.CndSet("APT::Install-Suggests", false); + Cnf.CndSet("Dir","/"); + + // State + Cnf.CndSet("Dir::State", STATE_DIR + 1); + Cnf.CndSet("Dir::State::lists","lists/"); + Cnf.CndSet("Dir::State::cdroms","cdroms.list"); + + // Cache + Cnf.CndSet("Dir::Cache", CACHE_DIR + 1); + Cnf.CndSet("Dir::Cache::archives","archives/"); + Cnf.CndSet("Dir::Cache::srcpkgcache","srcpkgcache.bin"); + Cnf.CndSet("Dir::Cache::pkgcache","pkgcache.bin"); + + // Configuration + Cnf.CndSet("Dir::Etc", CONF_DIR + 1); + Cnf.CndSet("Dir::Etc::sourcelist","sources.list"); + Cnf.CndSet("Dir::Etc::sourceparts","sources.list.d"); + Cnf.CndSet("Dir::Etc::main","apt.conf"); + Cnf.CndSet("Dir::Etc::netrc", "auth.conf"); + Cnf.CndSet("Dir::Etc::netrcparts", "auth.conf.d"); + Cnf.CndSet("Dir::Etc::parts","apt.conf.d"); + Cnf.CndSet("Dir::Etc::preferences","preferences"); + Cnf.CndSet("Dir::Etc::preferencesparts","preferences.d"); + Cnf.CndSet("Dir::Etc::trusted", "trusted.gpg"); + Cnf.CndSet("Dir::Etc::trustedparts","trusted.gpg.d"); + Cnf.CndSet("Dir::Bin::methods", LIBEXEC_DIR "/methods"); + Cnf.CndSet("Dir::Bin::solvers::",LIBEXEC_DIR "/solvers"); + Cnf.CndSet("Dir::Bin::planners::",LIBEXEC_DIR "/planners"); + Cnf.CndSet("Dir::Media::MountPath","/media/apt"); + + // State + Cnf.CndSet("Dir::Log", LOG_DIR + 1); + Cnf.CndSet("Dir::Log::Terminal","term.log"); + Cnf.CndSet("Dir::Log::History","history.log"); + Cnf.CndSet("Dir::Log::Planner","eipp.log.xz"); + + Cnf.Set("Dir::Ignore-Files-Silently::", "~$"); + Cnf.Set("Dir::Ignore-Files-Silently::", "\\.disabled$"); + Cnf.Set("Dir::Ignore-Files-Silently::", "\\.bak$"); + Cnf.Set("Dir::Ignore-Files-Silently::", "\\.dpkg-[a-z]+$"); + Cnf.Set("Dir::Ignore-Files-Silently::", "\\.ucf-[a-z]+$"); + Cnf.Set("Dir::Ignore-Files-Silently::", "\\.save$"); + Cnf.Set("Dir::Ignore-Files-Silently::", "\\.orig$"); + Cnf.Set("Dir::Ignore-Files-Silently::", "\\.distUpgrade$"); + + // Repository security + Cnf.CndSet("Acquire::AllowInsecureRepositories", false); + Cnf.CndSet("Acquire::AllowWeakRepositories", false); + Cnf.CndSet("Acquire::AllowDowngradeToInsecureRepositories", false); + + // Default cdrom mount point + Cnf.CndSet("Acquire::cdrom::mount", "/media/cdrom/"); + + // The default user we drop to in the methods + Cnf.CndSet("APT::Sandbox::User", "_apt"); + + Cnf.CndSet("Acquire::IndexTargets::deb::Packages::MetaKey", "$(COMPONENT)/binary-$(ARCHITECTURE)/Packages"); + Cnf.CndSet("Acquire::IndexTargets::deb::Packages::flatMetaKey", "Packages"); + Cnf.CndSet("Acquire::IndexTargets::deb::Packages::ShortDescription", "Packages"); + Cnf.CndSet("Acquire::IndexTargets::deb::Packages::Description", "$(RELEASE)/$(COMPONENT) $(ARCHITECTURE) Packages"); + Cnf.CndSet("Acquire::IndexTargets::deb::Packages::flatDescription", "$(RELEASE) Packages"); + Cnf.CndSet("Acquire::IndexTargets::deb::Packages::Optional", false); + Cnf.CndSet("Acquire::IndexTargets::deb::Translations::MetaKey", "$(COMPONENT)/i18n/Translation-$(LANGUAGE)"); + Cnf.CndSet("Acquire::IndexTargets::deb::Translations::flatMetaKey", "$(LANGUAGE)"); + Cnf.CndSet("Acquire::IndexTargets::deb::Translations::ShortDescription", "Translation-$(LANGUAGE)"); + Cnf.CndSet("Acquire::IndexTargets::deb::Translations::Description", "$(RELEASE)/$(COMPONENT) Translation-$(LANGUAGE)"); + Cnf.CndSet("Acquire::IndexTargets::deb::Translations::flatDescription", "$(RELEASE) Translation-$(LANGUAGE)"); + Cnf.CndSet("Acquire::IndexTargets::deb-src::Sources::MetaKey", "$(COMPONENT)/source/Sources"); + Cnf.CndSet("Acquire::IndexTargets::deb-src::Sources::flatMetaKey", "Sources"); + Cnf.CndSet("Acquire::IndexTargets::deb-src::Sources::ShortDescription", "Sources"); + Cnf.CndSet("Acquire::IndexTargets::deb-src::Sources::Description", "$(RELEASE)/$(COMPONENT) Sources"); + Cnf.CndSet("Acquire::IndexTargets::deb-src::Sources::flatDescription", "$(RELEASE) Sources"); + Cnf.CndSet("Acquire::IndexTargets::deb-src::Sources::Optional", false); + + Cnf.CndSet("Acquire::Changelogs::URI::Origin::Debian", "https://metadata.ftp-master.debian.org/changelogs/@CHANGEPATH@_changelog"); + Cnf.CndSet("Acquire::Changelogs::URI::Origin::Ubuntu", "https://changelogs.ubuntu.com/changelogs/pool/@CHANGEPATH@/changelog"); + Cnf.CndSet("Acquire::Changelogs::AlwaysOnline::Origin::Ubuntu", true); + + + Cnf.CndSet("DPkg::Path", "/usr/sbin:/usr/bin:/sbin:/bin"); + + // Read an alternate config file + _error->PushToStack(); + const char *Cfg = getenv("APT_CONFIG"); + if (Cfg != 0 && strlen(Cfg) != 0) + { + if (RealFileExists(Cfg) == true) + ReadConfigFile(Cnf, Cfg); + else + _error->WarningE("RealFileExists",_("Unable to read %s"),Cfg); + } + + // Read the configuration parts dir + std::string const Parts = Cnf.FindDir("Dir::Etc::parts", "/dev/null"); + if (DirectoryExists(Parts) == true) + ReadConfigDir(Cnf, Parts); + else if (APT::String::Endswith(Parts, "/dev/null") == false) + _error->WarningE("DirectoryExists",_("Unable to read %s"),Parts.c_str()); + + // Read the main config file + std::string const FName = Cnf.FindFile("Dir::Etc::main", "/dev/null"); + if (RealFileExists(FName) == true) + ReadConfigFile(Cnf, FName); + + if (Cnf.FindB("Debug::pkgInitConfig",false) == true) + Cnf.Dump(); + +#ifdef APT_DOMAIN + if (Cnf.Exists("Dir::Locale")) + { + bindtextdomain(APT_DOMAIN,Cnf.FindDir("Dir::Locale").c_str()); + bindtextdomain(textdomain(0),Cnf.FindDir("Dir::Locale").c_str()); + } +#endif + + auto const good = _error->PendingError() == false; + _error->MergeWithStack(); + return good; +} + /*}}}*/ +// pkgInitSystem - Initialize the _system class /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgInitSystem(Configuration &Cnf,pkgSystem *&Sys) +{ + Sys = 0; + std::string Label = Cnf.Find("Apt::System",""); + if (Label.empty() == false) + { + Sys = pkgSystem::GetSystem(Label.c_str()); + if (Sys == 0) + return _error->Error(_("Packaging system '%s' is not supported"),Label.c_str()); + } + else + { + signed MaxScore = 0; + for (unsigned I = 0; I != pkgSystem::GlobalListLen; I++) + { + signed Score = pkgSystem::GlobalList[I]->Score(Cnf); + if (Score > MaxScore) + { + MaxScore = Score; + Sys = pkgSystem::GlobalList[I]; + } + } + + if (Sys == 0) + return _error->Error(_("Unable to determine a suitable packaging system type")); + } + + if (pkgInitArchTupleMap() == false) + return false; + + return Sys->Initialize(Cnf); +} + /*}}}*/ diff --git a/apt-pkg/init.h b/apt-pkg/init.h new file mode 100644 index 0000000..d062392 --- /dev/null +++ b/apt-pkg/init.h @@ -0,0 +1,32 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Init - Initialize the package library + + This function must be called to configure the config class before + calling many APT library functions. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_INIT_H +#define PKGLIB_INIT_H + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/configuration.h> +#include <apt-pkg/pkgsystem.h> +#endif +#ifndef APT_10_CLEANER_HEADERS +#include <apt-pkg/macros.h> +#endif + +class pkgSystem; +class Configuration; + +extern const char *pkgVersion; +extern const char *pkgLibVersion; + +bool pkgInitConfig(Configuration &Cnf); +bool pkgInitSystem(Configuration &Cnf,pkgSystem *&Sys); + +#endif diff --git a/apt-pkg/install-progress.cc b/apt-pkg/install-progress.cc new file mode 100644 index 0000000..2e8fac2 --- /dev/null +++ b/apt-pkg/install-progress.cc @@ -0,0 +1,425 @@ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/install-progress.h> +#include <apt-pkg/strutl.h> + +#include <algorithm> +#include <cmath> +#include <iostream> +#include <sstream> +#include <vector> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include <apti18n.h> + +namespace APT { +namespace Progress { + +PackageManager::PackageManager() : d(NULL), percentage(0.0), last_reported_progress(-1) {} +PackageManager::~PackageManager() {} + +/* Return a APT::Progress::PackageManager based on the global + * apt configuration (i.e. APT::Status-Fd and APT::Status-deb822-Fd) + */ +PackageManager* PackageManagerProgressFactory() +{ + // select the right progress + int status_fd = _config->FindI("APT::Status-Fd", -1); + int status_deb822_fd = _config->FindI("APT::Status-deb822-Fd", -1); + + APT::Progress::PackageManager *progress = NULL; + if (status_deb822_fd > 0) + progress = new APT::Progress::PackageManagerProgressDeb822Fd( + status_deb822_fd); + else if (status_fd > 0) + progress = new APT::Progress::PackageManagerProgressFd(status_fd); + else if(_config->FindB("Dpkg::Progress-Fancy", false) == true) + progress = new APT::Progress::PackageManagerFancy(); + else if (_config->FindB("Dpkg::Progress", + _config->FindB("DpkgPM::Progress", false)) == true) + progress = new APT::Progress::PackageManagerText(); + else + progress = new APT::Progress::PackageManager(); + return progress; +} + +bool PackageManager::StatusChanged(std::string /*PackageName*/, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string /*HumanReadableAction*/) +{ + int reporting_steps = _config->FindI("DpkgPM::Reporting-Steps", 1); + percentage = StepsDone/(double)TotalSteps * 100.0; + strprintf(progress_str, _("Progress: [%3li%%]"), std::lround(percentage)); + + if(percentage < (last_reported_progress + reporting_steps)) + return false; + + return true; +} + +PackageManagerProgressFd::PackageManagerProgressFd(int progress_fd) + : d(NULL), StepsDone(0), StepsTotal(1) +{ + OutStatusFd = progress_fd; +} +PackageManagerProgressFd::~PackageManagerProgressFd() {} + +void PackageManagerProgressFd::WriteToStatusFd(std::string s) +{ + if(OutStatusFd <= 0) + return; + FileFd::Write(OutStatusFd, s.c_str(), s.size()); +} + +static std::string GetProgressFdString(char const * const status, + char const * const pkg, unsigned long long Done, + unsigned long long Total, char const * const msg) +{ + float const progress{Done / static_cast<float>(Total) * 100}; + std::ostringstream str; + str.imbue(std::locale::classic()); + str.precision(4); + str << status << ':' << pkg << ':' << std::fixed << progress << ':' << msg << '\n'; + return str.str(); +} + +void PackageManagerProgressFd::StartDpkg() +{ + if(OutStatusFd <= 0) + return; + + // FIXME: use SetCloseExec here once it taught about throwing + // exceptions instead of doing _exit(100) on failure + fcntl(OutStatusFd,F_SETFD,FD_CLOEXEC); + + // send status information that we are about to fork dpkg + WriteToStatusFd(GetProgressFdString("pmstatus", "dpkg-exec", StepsDone, StepsTotal, _("Running dpkg"))); +} + +void PackageManagerProgressFd::Stop() +{ +} + +void PackageManagerProgressFd::Error(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ErrorMessage) +{ + WriteToStatusFd(GetProgressFdString("pmerror", PackageName.c_str(), + StepsDone, TotalSteps, ErrorMessage.c_str())); +} + +void PackageManagerProgressFd::ConffilePrompt(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ConfMessage) +{ + WriteToStatusFd(GetProgressFdString("pmconffile", PackageName.c_str(), + StepsDone, TotalSteps, ConfMessage.c_str())); +} + + +bool PackageManagerProgressFd::StatusChanged(std::string PackageName, + unsigned int xStepsDone, + unsigned int xTotalSteps, + std::string pkg_action) +{ + StepsDone = xStepsDone; + StepsTotal = xTotalSteps; + + WriteToStatusFd(GetProgressFdString("pmstatus", StringSplit(PackageName, ":")[0].c_str(), + StepsDone, StepsTotal, pkg_action.c_str())); + + if(_config->FindB("Debug::APT::Progress::PackageManagerFd", false) == true) + std::cerr << "progress: " << PackageName << " " << xStepsDone + << " " << xTotalSteps << " " << pkg_action + << std::endl; + + + return true; +} + + +PackageManagerProgressDeb822Fd::PackageManagerProgressDeb822Fd(int progress_fd) + : d(NULL), StepsDone(0), StepsTotal(1) +{ + OutStatusFd = progress_fd; +} +PackageManagerProgressDeb822Fd::~PackageManagerProgressDeb822Fd() {} + +void PackageManagerProgressDeb822Fd::WriteToStatusFd(std::string s) +{ + FileFd::Write(OutStatusFd, s.c_str(), s.size()); +} + +static std::string GetProgressDeb822String(char const * const status, + char const * const pkg, unsigned long long Done, + unsigned long long Total, char const * const msg) +{ + float const progress{Done / static_cast<float>(Total) * 100}; + std::ostringstream str; + str.imbue(std::locale::classic()); + str.precision(4); + str << "Status: " << status << '\n'; + if (pkg != nullptr) + str << "Package: " << pkg << '\n'; + str << "Percent: " << std::fixed << progress << '\n' + << "Message: " << msg << "\n\n"; + return str.str(); +} + +void PackageManagerProgressDeb822Fd::StartDpkg() +{ + // FIXME: use SetCloseExec here once it taught about throwing + // exceptions instead of doing _exit(100) on failure + fcntl(OutStatusFd,F_SETFD,FD_CLOEXEC); + + WriteToStatusFd(GetProgressDeb822String("progress", nullptr, StepsDone, StepsTotal, _("Running dpkg"))); +} + +void PackageManagerProgressDeb822Fd::Stop() +{ +} + +void PackageManagerProgressDeb822Fd::Error(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ErrorMessage) +{ + WriteToStatusFd(GetProgressDeb822String("Error", PackageName.c_str(), StepsDone, TotalSteps, ErrorMessage.c_str())); +} + +void PackageManagerProgressDeb822Fd::ConffilePrompt(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ConfMessage) +{ + WriteToStatusFd(GetProgressDeb822String("ConfFile", PackageName.c_str(), StepsDone, TotalSteps, ConfMessage.c_str())); +} + + +bool PackageManagerProgressDeb822Fd::StatusChanged(std::string PackageName, + unsigned int xStepsDone, + unsigned int xTotalSteps, + std::string message) +{ + StepsDone = xStepsDone; + StepsTotal = xTotalSteps; + + WriteToStatusFd(GetProgressDeb822String("progress", PackageName.c_str(), StepsDone, StepsTotal, message.c_str())); + return true; +} + + +PackageManagerFancy::PackageManagerFancy() + : d(NULL), child_pty(-1) +{ + // setup terminal size + old_SIGWINCH = signal(SIGWINCH, PackageManagerFancy::staticSIGWINCH); + instances.push_back(this); +} +std::vector<PackageManagerFancy*> PackageManagerFancy::instances; + +PackageManagerFancy::~PackageManagerFancy() +{ + instances.erase(find(instances.begin(), instances.end(), this)); + signal(SIGWINCH, old_SIGWINCH); +} + +void PackageManagerFancy::staticSIGWINCH(int signum) +{ + std::vector<PackageManagerFancy *>::const_iterator I; + for(I = instances.begin(); I != instances.end(); ++I) + (*I)->HandleSIGWINCH(signum); +} + +PackageManagerFancy::TermSize +PackageManagerFancy::GetTerminalSize() +{ + struct winsize win; + PackageManagerFancy::TermSize s = { 0, 0 }; + + // FIXME: get from "child_pty" instead? + if(ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&win) != 0) + return s; + + if(_config->FindB("Debug::InstallProgress::Fancy", false) == true) + std::cerr << "GetTerminalSize: " << win.ws_row << " x " << win.ws_col << std::endl; + + s.rows = win.ws_row; + s.columns = win.ws_col; + return s; +} + +void PackageManagerFancy::SetupTerminalScrollArea(int nr_rows) +{ + if(_config->FindB("Debug::InstallProgress::Fancy", false) == true) + std::cerr << "SetupTerminalScrollArea: " << nr_rows << std::endl; + + if (unlikely(nr_rows <= 1)) + return; + + // scroll down a bit to avoid visual glitch when the screen + // area shrinks by one row + std::cout << "\n"; + + // save cursor + std::cout << "\0337"; + + // set scroll region (this will place the cursor in the top left) + std::cout << "\033[0;" << std::to_string(nr_rows - 1) << "r"; + + // restore cursor but ensure its inside the scrolling area + std::cout << "\0338"; + static const char *move_cursor_up = "\033[1A"; + std::cout << move_cursor_up; + + // ensure its flushed + std::flush(std::cout); + + // setup tty size to ensure xterm/linux console are working properly too + // see bug #731738 + struct winsize win; + if (ioctl(child_pty, TIOCGWINSZ, (char *)&win) != -1) + { + win.ws_row = nr_rows - 1; + ioctl(child_pty, TIOCSWINSZ, (char *)&win); + } +} + +void PackageManagerFancy::HandleSIGWINCH(int) +{ + int const nr_terminal_rows = GetTerminalSize().rows; + SetupTerminalScrollArea(nr_terminal_rows); + DrawStatusLine(); +} + +void PackageManagerFancy::Start(int a_child_pty) +{ + child_pty = a_child_pty; + int const nr_terminal_rows = GetTerminalSize().rows; + SetupTerminalScrollArea(nr_terminal_rows); +} + +void PackageManagerFancy::Stop() +{ + int const nr_terminal_rows = GetTerminalSize().rows; + if (nr_terminal_rows > 0) + { + SetupTerminalScrollArea(nr_terminal_rows + 1); + + // override the progress line (sledgehammer) + static const char* clear_screen_below_cursor = "\033[J"; + std::cout << clear_screen_below_cursor; + std::flush(std::cout); + } + child_pty = -1; +} + +std::string +PackageManagerFancy::GetTextProgressStr(float Percent, int OutputSize) +{ + std::string output; + if (unlikely(OutputSize < 3)) + return output; + + int const BarSize = OutputSize - 2; // bar without the leading "[" and trailing "]" + int const BarDone = std::max(0, std::min(BarSize, static_cast<int>(std::floor(Percent * BarSize)))); + output.append("["); + std::fill_n(std::fill_n(std::back_inserter(output), BarDone, '#'), BarSize - BarDone, '.'); + output.append("]"); + return output; +} + +bool PackageManagerFancy::StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction) +{ + if (!PackageManager::StatusChanged(PackageName, StepsDone, TotalSteps, + HumanReadableAction)) + return false; + + return DrawStatusLine(); +} +bool PackageManagerFancy::DrawStatusLine() +{ + PackageManagerFancy::TermSize const size = GetTerminalSize(); + if (unlikely(size.rows < 1 || size.columns < 1)) + return false; + + static std::string save_cursor = "\0337"; + static std::string restore_cursor = "\0338"; + + // green + static std::string set_bg_color = DeQuoteString( + _config->Find("Dpkg::Progress-Fancy::Progress-fg", "%1b[42m")); + // black + static std::string set_fg_color = DeQuoteString( + _config->Find("Dpkg::Progress-Fancy::Progress-bg", "%1b[30m")); + + static std::string restore_bg = "\033[49m"; + static std::string restore_fg = "\033[39m"; + + std::cout << save_cursor + // move cursor position to last row + << "\033[" << std::to_string(size.rows) << ";0f" + << set_bg_color + << set_fg_color + << progress_str + << restore_bg + << restore_fg; + std::flush(std::cout); + + // draw text progress bar + if (_config->FindB("Dpkg::Progress-Fancy::Progress-Bar", true)) + { + int padding = 4; + auto const progressbar_size = size.columns - padding - progress_str.size(); + auto const current_percent = percentage / 100.0f; + std::cout << " " + << GetTextProgressStr(current_percent, progressbar_size) + << " "; + std::flush(std::cout); + } + + // restore + std::cout << restore_cursor; + std::flush(std::cout); + + last_reported_progress = percentage; + + return true; +} + +bool PackageManagerText::StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction) +{ + if (!PackageManager::StatusChanged(PackageName, StepsDone, TotalSteps, HumanReadableAction)) + return false; + + std::cout << progress_str << "\r\n"; + std::flush(std::cout); + + last_reported_progress = percentage; + + return true; +} + +PackageManagerText::PackageManagerText() : PackageManager(), d(NULL) {} +PackageManagerText::~PackageManagerText() {} + + + + +} // namespace progress +} // namespace apt diff --git a/apt-pkg/install-progress.h b/apt-pkg/install-progress.h new file mode 100644 index 0000000..bcc0b61 --- /dev/null +++ b/apt-pkg/install-progress.h @@ -0,0 +1,176 @@ +#ifndef PKGLIB_IPROGRESS_H +#define PKGLIB_IPROGRESS_H + +#include <apt-pkg/macros.h> + +#include <string> +#include <vector> +#include <signal.h> +#include <unistd.h> + +namespace APT { +namespace Progress { + + class PackageManager; + PackageManager* PackageManagerProgressFactory(); + + class PackageManager + { + private: + /** \brief dpointer placeholder */ + void * const d; + + protected: + std::string progress_str; + float percentage; + int last_reported_progress; + + public: + PackageManager(); + virtual ~PackageManager(); + + /* Global Start/Stop */ + virtual void Start(int /*child_pty*/=-1) {}; + virtual void Stop() {}; + + /* When dpkg is invoked (may happen multiple times for each + * install/remove block + */ + virtual void StartDpkg() {}; + + virtual pid_t fork() {return ::fork(); }; + + virtual void Pulse() {}; + virtual long GetPulseInterval() { + return 50000000; + }; + + virtual bool StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction); + virtual void Error(std::string /*PackageName*/, + unsigned int /*StepsDone*/, + unsigned int /*TotalSteps*/, + std::string /*ErrorMessage*/) {} + virtual void ConffilePrompt(std::string /*PackageName*/, + unsigned int /*StepsDone*/, + unsigned int /*TotalSteps*/, + std::string /*ConfMessage*/) {} + }; + + class PackageManagerProgressFd : public PackageManager + { + void * const d; + protected: + int OutStatusFd; + int StepsDone; + int StepsTotal; + void WriteToStatusFd(std::string msg); + + public: + explicit PackageManagerProgressFd(int progress_fd); + virtual ~PackageManagerProgressFd(); + + virtual void StartDpkg() APT_OVERRIDE; + virtual void Stop() APT_OVERRIDE; + + virtual bool StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction) APT_OVERRIDE; + virtual void Error(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ErrorMessage) APT_OVERRIDE; + virtual void ConffilePrompt(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ConfMessage) APT_OVERRIDE; + + }; + + class PackageManagerProgressDeb822Fd : public PackageManager + { + void * const d; + protected: + int OutStatusFd; + int StepsDone; + int StepsTotal; + void WriteToStatusFd(std::string msg); + + public: + explicit PackageManagerProgressDeb822Fd(int progress_fd); + virtual ~PackageManagerProgressDeb822Fd(); + + virtual void StartDpkg() APT_OVERRIDE; + virtual void Stop() APT_OVERRIDE; + + virtual bool StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction) APT_OVERRIDE; + virtual void Error(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ErrorMessage) APT_OVERRIDE; + virtual void ConffilePrompt(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string ConfMessage) APT_OVERRIDE; + }; + + class PackageManagerFancy : public PackageManager + { + void * const d; + private: + APT_HIDDEN static void staticSIGWINCH(int); + static std::vector<PackageManagerFancy*> instances; + APT_HIDDEN bool DrawStatusLine(); + + protected: + void SetupTerminalScrollArea(int nr_rows); + void HandleSIGWINCH(int); + + typedef struct { + int rows; + int columns; + } TermSize; + TermSize GetTerminalSize(); + + sighandler_t old_SIGWINCH; + int child_pty; + + public: + PackageManagerFancy(); + virtual ~PackageManagerFancy(); + virtual void Start(int child_pty=-1) APT_OVERRIDE; + virtual void Stop() APT_OVERRIDE; + virtual bool StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction) APT_OVERRIDE; + + // return a progress bar of the given size for the given progress + // percent between 0.0 and 1.0 in the form "[####...]" + static std::string GetTextProgressStr(float percent, int OutputSize); + }; + + class PackageManagerText : public PackageManager + { + void * const d; + public: + virtual bool StatusChanged(std::string PackageName, + unsigned int StepsDone, + unsigned int TotalSteps, + std::string HumanReadableAction) APT_OVERRIDE; + + PackageManagerText(); + virtual ~PackageManagerText(); + }; + + +} // namespace Progress +} // namespace APT + +#endif diff --git a/apt-pkg/metaindex.cc b/apt-pkg/metaindex.cc new file mode 100644 index 0000000..8cbdb5e --- /dev/null +++ b/apt-pkg/metaindex.cc @@ -0,0 +1,176 @@ +// Include Files /*{{{*/ +#include <apt-pkg/indexfile.h> +#include <apt-pkg/metaindex.h> +#include <apt-pkg/pkgcachegen.h> + +#include <apt-pkg/debmetaindex.h> + +#include <string> +#include <vector> + /*}}}*/ + +class metaIndexPrivate /*{{{*/ +{ + public: + std::string Origin; + std::string Label; + std::string Version; + signed short DefaultPin; + std::string ReleaseNotes; +}; + /*}}}*/ + +std::string metaIndex::Describe() const +{ + return "Release"; +} + +pkgCache::RlsFileIterator metaIndex::FindInCache(pkgCache &Cache, bool const) const +{ + return pkgCache::RlsFileIterator(Cache); +} + +bool metaIndex::Merge(pkgCacheGenerator &Gen,OpProgress *) const +{ + return Gen.SelectReleaseFile("", ""); +} + +metaIndex::metaIndex(std::string const &URI, std::string const &Dist, + char const * const Type) +: d(new metaIndexPrivate()), Indexes(NULL), Type(Type), URI(URI), Dist(Dist), Trusted(TRI_UNSET), + Date(0), ValidUntil(0), SupportsAcquireByHash(false), LoadedSuccessfully(TRI_UNSET) +{ + /* nothing */ +} + +metaIndex::~metaIndex() +{ + if (Indexes != 0) + { + for (std::vector<pkgIndexFile *>::iterator I = (*Indexes).begin(); + I != (*Indexes).end(); ++I) + delete *I; + delete Indexes; + } + for (auto const &E: Entries) + delete E.second; + delete d; +} + +// one line Getters for public fields /*{{{*/ +APT_PURE std::string metaIndex::GetURI() const { return URI; } +APT_PURE std::string metaIndex::GetDist() const { return Dist; } +APT_PURE const char* metaIndex::GetType() const { return Type; } +APT_PURE metaIndex::TriState metaIndex::GetTrusted() const { return Trusted; } +APT_PURE std::string metaIndex::GetSignedBy() const { return SignedBy; } +APT_PURE std::string metaIndex::GetOrigin() const { return d->Origin; } +APT_PURE std::string metaIndex::GetLabel() const { return d->Label; } +APT_PURE std::string metaIndex::GetVersion() const { return d->Version; } +APT_PURE std::string metaIndex::GetCodename() const { return Codename; } +APT_PURE std::string metaIndex::GetSuite() const { return Suite; } +APT_PURE std::string metaIndex::GetReleaseNotes() const { return d->ReleaseNotes; } +APT_PURE signed short metaIndex::GetDefaultPin() const { return d->DefaultPin; } +APT_PURE bool metaIndex::GetSupportsAcquireByHash() const { return SupportsAcquireByHash; } +APT_PURE time_t metaIndex::GetValidUntil() const { return ValidUntil; } +APT_PURE time_t metaIndex::GetNotBefore() const +{ + debReleaseIndex const *const deb = dynamic_cast<debReleaseIndex const *>(this); + if (deb != nullptr) + return deb->GetNotBefore(); + return 0; +} +APT_PURE time_t metaIndex::GetDate() const { return this->Date; } +APT_PURE metaIndex::TriState metaIndex::GetLoadedSuccessfully() const { return LoadedSuccessfully; } +APT_PURE std::string metaIndex::GetExpectedDist() const { return Dist; } + /*}}}*/ +bool metaIndex::CheckDist(string const &MaybeDist) const /*{{{*/ +{ + if (MaybeDist.empty() || this->Codename == MaybeDist || this->Suite == MaybeDist) + return true; + + std::string Transformed = MaybeDist; + if (Transformed == "../project/experimental") + Transformed = "experimental"; + + auto const pos = Transformed.rfind('/'); + if (pos != string::npos) + Transformed = Transformed.substr(0, pos); + + if (Transformed == ".") + Transformed.clear(); + + return Transformed.empty() || this->Codename == Transformed || this->Suite == Transformed; +} + /*}}}*/ +APT_PURE metaIndex::checkSum *metaIndex::Lookup(string const &MetaKey) const /*{{{*/ +{ + std::map<std::string, metaIndex::checkSum* >::const_iterator sum = Entries.find(MetaKey); + if (sum == Entries.end()) + return NULL; + return sum->second; +} + /*}}}*/ +APT_PURE bool metaIndex::Exists(string const &MetaKey) const /*{{{*/ +{ + return Entries.find(MetaKey) != Entries.end(); +} + /*}}}*/ +std::vector<std::string> metaIndex::MetaKeys() const /*{{{*/ +{ + std::vector<std::string> keys; + std::map<string,checkSum *>::const_iterator I = Entries.begin(); + while(I != Entries.end()) { + keys.push_back((*I).first); + ++I; + } + return keys; +} + /*}}}*/ +void metaIndex::swapLoad(metaIndex * const OldMetaIndex) /*{{{*/ +{ + std::swap(SignedBy, OldMetaIndex->SignedBy); + std::swap(Suite, OldMetaIndex->Suite); + std::swap(Codename, OldMetaIndex->Codename); + std::swap(Date, OldMetaIndex->Date); + std::swap(ValidUntil, OldMetaIndex->ValidUntil); + std::swap(SupportsAcquireByHash, OldMetaIndex->SupportsAcquireByHash); + std::swap(Entries, OldMetaIndex->Entries); + std::swap(LoadedSuccessfully, OldMetaIndex->LoadedSuccessfully); + + OldMetaIndex->SetOrigin(d->Origin); + OldMetaIndex->SetLabel(d->Label); + OldMetaIndex->SetVersion(d->Version); + OldMetaIndex->SetDefaultPin(d->DefaultPin); +} + /*}}}*/ + +bool metaIndex::IsArchitectureSupported(std::string const &arch) const /*{{{*/ +{ + debReleaseIndex const * const deb = dynamic_cast<debReleaseIndex const *>(this); + if (deb != NULL) + return deb->IsArchitectureSupported(arch); + return true; +} + /*}}}*/ +bool metaIndex::IsArchitectureAllSupportedFor(IndexTarget const &target) const/*{{{*/ +{ + debReleaseIndex const * const deb = dynamic_cast<debReleaseIndex const *>(this); + if (deb != NULL) + return deb->IsArchitectureAllSupportedFor(target); + return true; +} + /*}}}*/ +bool metaIndex::HasSupportForComponent(std::string const &component) const/*{{{*/ +{ + debReleaseIndex const * const deb = dynamic_cast<debReleaseIndex const *>(this); + if (deb != NULL) + return deb->HasSupportForComponent(component); + return true; +} + /*}}}*/ + +void metaIndex::SetOrigin(std::string const &origin) { d->Origin = origin; } +void metaIndex::SetLabel(std::string const &label) { d->Label = label; } +void metaIndex::SetVersion(std::string const &version) { d->Version = version; } +void metaIndex::SetDefaultPin(signed short const defaultpin) { d->DefaultPin = defaultpin; } +void metaIndex::SetReleaseNotes(std::string const ¬es) { d->ReleaseNotes = notes; } diff --git a/apt-pkg/metaindex.h b/apt-pkg/metaindex.h new file mode 100644 index 0000000..0866430 --- /dev/null +++ b/apt-pkg/metaindex.h @@ -0,0 +1,131 @@ +#ifndef PKGLIB_METAINDEX_H +#define PKGLIB_METAINDEX_H + +#include <apt-pkg/indexfile.h> +#include <apt-pkg/init.h> + +#include <stddef.h> + +#include <string> +#include <vector> + +#ifndef APT_10_CLEANER_HEADERS +#include <apt-pkg/pkgcache.h> +class pkgCacheGenerator; +class OpProgress; +#endif +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/pkgrecords.h> +#include <apt-pkg/srcrecords.h> +using std::string; +#endif + +class pkgAcquire; +class IndexTarget; +class pkgCacheGenerator; +class OpProgress; + +class metaIndexPrivate; + +class metaIndex +{ +public: + APT_IGNORE_DEPRECATED_PUSH + struct checkSum + { + std::string MetaKeyFilename; + HashStringList Hashes; + unsigned long long Size; + + APT_DEPRECATED_MSG("Use the HashStringList member Hashes instead of a hardcoded HashString") HashString Hash; + }; + APT_IGNORE_DEPRECATED_POP + + enum APT_HIDDEN TriState { + TRI_YES, TRI_DONTCARE, TRI_NO, TRI_UNSET + }; +private: + metaIndexPrivate * const d; +protected: + std::vector <pkgIndexFile *> *Indexes; + // parsed from the sources.list + const char *Type; + std::string URI; + std::string Dist; + TriState Trusted; + std::string SignedBy; + + // parsed from a file + std::string Suite; + std::string Codename; + time_t Date; + time_t ValidUntil; + bool SupportsAcquireByHash; + std::map<std::string, checkSum *> Entries; + TriState LoadedSuccessfully; + +public: + // Various accessors + std::string GetURI() const; + std::string GetDist() const; + const char* GetType() const; + TriState GetTrusted() const; + std::string GetSignedBy() const; + + std::string GetOrigin() const; + std::string GetLabel() const; + std::string GetVersion() const; + std::string GetCodename() const; + std::string GetSuite() const; + std::string GetReleaseNotes() const; + signed short GetDefaultPin() const; + bool GetSupportsAcquireByHash() const; + time_t GetValidUntil() const; + time_t GetDate() const; + APT_HIDDEN time_t GetNotBefore() const; // FIXME make virtual + + std::string GetExpectedDist() const; + bool CheckDist(std::string const &MaybeDist) const; + + // Interface for acquire + virtual std::string Describe() const; + virtual std::string ArchiveURI(std::string const& File) const = 0; + virtual bool GetIndexes(pkgAcquire *Owner, bool const &GetAll=false) = 0; + virtual std::vector<IndexTarget> GetIndexTargets() const = 0; + virtual std::vector<pkgIndexFile *> *GetIndexFiles() = 0; + virtual bool IsTrusted() const = 0; + virtual bool Load(std::string const &Filename, std::string * const ErrorText) = 0; + /** @return a new metaIndex object based on this one, but without information from #Load */ + virtual metaIndex * UnloadedClone() const = 0; + // the given metaIndex is potentially invalid after this call and should be deleted + void swapLoad(metaIndex * const OldMetaIndex); + + // Lookup functions for parsed Hashes + checkSum *Lookup(std::string const &MetaKey) const; + /** \brief tests if a checksum for this file is available */ + bool Exists(std::string const &MetaKey) const; + std::vector<std::string> MetaKeys() const; + TriState GetLoadedSuccessfully() const; + + // Interfaces for pkgCacheGen + virtual pkgCache::RlsFileIterator FindInCache(pkgCache &Cache, bool const ModifyCheck) const; + virtual bool Merge(pkgCacheGenerator &Gen,OpProgress *Prog) const; + + + metaIndex(std::string const &URI, std::string const &Dist, + char const * const Type); + virtual ~metaIndex(); + + // FIXME: make virtual on next abi break + bool IsArchitectureSupported(std::string const &arch) const; + bool IsArchitectureAllSupportedFor(IndexTarget const &target) const; + bool HasSupportForComponent(std::string const &component) const; + // FIXME: should be members of the class on abi break + APT_HIDDEN void SetOrigin(std::string const &origin); + APT_HIDDEN void SetLabel(std::string const &label); + APT_HIDDEN void SetVersion(std::string const &version); + APT_HIDDEN void SetDefaultPin(signed short const defaultpin); + APT_HIDDEN void SetReleaseNotes(std::string const ¬es); +}; + +#endif diff --git a/apt-pkg/orderlist.cc b/apt-pkg/orderlist.cc new file mode 100644 index 0000000..20631bf --- /dev/null +++ b/apt-pkg/orderlist.cc @@ -0,0 +1,1138 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Order List - Represents and Manipulates an ordered list of packages. + + A list of packages can be ordered by a number of conflicting criteria + each given a specific priority. Each package also has a set of flags + indicating some useful things about it that are derived in the + course of sorting. The pkgPackageManager class uses this class for + all of it's installation ordering needs. + + This is a modified version of Manoj's Routine B. It consists of four + independent ordering algorithms that can be applied at for different + points in the ordering. By applying progressivly fewer ordering + operations it is possible to give each consideration it's own + priority and create an order that satisfies the lowest applicable + consideration. + + The rules for unpacking ordering are: + 1) Unpacking ignores Depends: on all packages + 2) Unpacking requires Conflicts: on -ALL- packages to be satisfied + 3) Unpacking requires PreDepends: on this package only to be satisfied + 4) Removing requires that no packages depend on the package to be + removed. + + And the rule for configuration ordering is: + 1) Configuring requires that the Depends: of the package be satisfied + Conflicts+PreDepends are ignored because unpacking says they are + already correct [exageration, it does check but we need not be + concerned] + + And some features that are valuable for unpacking ordering. + f1) Unpacking a new package should advoid breaking dependencies of + configured packages + f2) Removal should not require a force, corrolory of f1 + f3) Unpacking should order by depends rather than fall back to random + ordering. + + Each of the features can be enabled in the sorting routine at an + arbitrary priority to give quite abit of control over the final unpacking + order. + + The rules listed above may never be violated and are called Critical. + When a critical rule is violated then a loop condition is recorded + and will have to be delt with in the caller. + + The ordering keeps two lists, the main list and the 'After List'. The + purpose of the after list is to allow packages to be delayed. This is done + by setting the after flag on the package. Any package which requires this + package to be ordered before will inherit the after flag and so on. This + is used for CD swap ordering where all packages on a second CD have the + after flag set. This forces them and all their dependents to be ordered + toward the end. + + There are complications in this algorithm when presented with cycles. + For all known practical cases it works, all cases where it doesn't work + is fixable by tweaking the package descriptions. However, it should be + possible to improve this further to make some better choices when + presented with cycles. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/error.h> +#include <apt-pkg/orderlist.h> +#include <apt-pkg/pkgcache.h> + +#include <algorithm> +#include <iostream> +#include <stdlib.h> +#include <string.h> + /*}}}*/ + +using namespace std; + +// OrderList::pkgOrderList - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgOrderList::pkgOrderList(pkgDepCache *pCache) : d(NULL), Cache(*pCache), + Primary(NULL), Secondary(NULL), + RevDepends(NULL), Remove(NULL), + AfterEnd(NULL), FileList(NULL), + LoopCount(-1), Depth(0) +{ + Debug = _config->FindB("Debug::pkgOrderList",false); + + /* Construct the arrays, egcs 1.0.1 bug requires the package count + hack */ + auto const Size = Cache.Head().PackageCount; + Flags = new unsigned short[Size]; + End = List = new Package *[Size]; + memset(Flags,0,sizeof(*Flags)*Size); +} + /*}}}*/ +// OrderList::~pkgOrderList - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgOrderList::~pkgOrderList() +{ + delete [] List; + delete [] Flags; +} + /*}}}*/ +// OrderList::IsMissing - Check if a file is missing /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgOrderList::IsMissing(PkgIterator Pkg) +{ + // Skip packages to erase + if (Cache[Pkg].Delete() == true) + return false; + + // Skip Packages that need configure only. + if ((Pkg.State() == pkgCache::PkgIterator::NeedsConfigure || + Pkg.State() == pkgCache::PkgIterator::NeedsNothing) && + Cache[Pkg].Keep() == true) + return false; + + if (FileList == 0) + return false; + + if (FileList[Pkg->ID].empty() == false) + return false; + + return true; +} + /*}}}*/ +// OrderList::DoRun - Does an order run /*{{{*/ +// --------------------------------------------------------------------- +/* The caller is expected to have setup the desired probe state */ +bool pkgOrderList::DoRun() +{ + // Temp list + unsigned long Size = Cache.Head().PackageCount; + std::unique_ptr<Package *[]> NList(new Package *[Size]); + std::unique_ptr<Package *[]> AfterList(new Package *[Size]); + AfterEnd = AfterList.get(); + + Depth = 0; + WipeFlags(Added | AddPending | Loop | InList); + + for (iterator I = List; I != End; ++I) + Flag(*I,InList); + + // Rebuild the main list into the temp list. + iterator OldEnd = End; + End = NList.get(); + for (iterator I = List; I != OldEnd; ++I) + if (VisitNode(PkgIterator(Cache,*I), "DoRun") == false) + { + End = OldEnd; + return false; + } + + // Copy the after list to the end of the main list + for (Package **I = AfterList.get(); I != AfterEnd; I++) + *End++ = *I; + + // Swap the main list to the new list + delete [] List; + List = NList.release(); + return true; +} + /*}}}*/ +// OrderList::OrderCritical - Perform critical unpacking ordering /*{{{*/ +// --------------------------------------------------------------------- +/* This performs predepends and immediate configuration ordering only. + This is termed critical unpacking ordering. Any loops that form are + fatal and indicate that the packages cannot be installed. */ +bool pkgOrderList::OrderCritical() +{ + FileList = 0; + + Primary = &pkgOrderList::DepUnPackPreD; + Secondary = 0; + RevDepends = 0; + Remove = 0; + LoopCount = 0; + + // Sort + std::sort(List,End, [this](Package *a, Package *b) { return OrderCompareB(a, b) < 0; } ); + + if (DoRun() == false) + return false; + + if (LoopCount != 0) + return _error->Error("Fatal, predepends looping detected"); + + if (Debug == true) + { + clog << "** Critical Unpack ordering done" << endl; + + for (iterator I = List; I != End; ++I) + { + PkgIterator P(Cache,*I); + if (IsNow(P) == true) + clog << " " << P.FullName() << ' ' << IsMissing(P) << ',' << IsFlag(P,After) << endl; + } + } + + return true; +} + /*}}}*/ +// OrderList::OrderUnpack - Perform complete unpacking ordering /*{{{*/ +// --------------------------------------------------------------------- +/* This performs complete unpacking ordering and creates an order that is + suitable for unpacking */ +bool pkgOrderList::OrderUnpack(string *FileList) +{ + this->FileList = FileList; + + // Setup the after flags + if (FileList != 0) + { + WipeFlags(After); + + // Set the inlist flag + for (iterator I = List; I != End; ++I) + { + PkgIterator P(Cache,*I); + if (IsMissing(P) == true && IsNow(P) == true) + Flag(*I,After); + } + } + + Primary = &pkgOrderList::DepUnPackCrit; + Secondary = &pkgOrderList::DepConfigure; + RevDepends = &pkgOrderList::DepUnPackDep; + Remove = &pkgOrderList::DepRemove; + LoopCount = -1; + + // Sort + std::sort(List,End, [this](Package *a, Package *b) { return OrderCompareA(a, b) < 0; }); + + if (Debug == true) + clog << "** Pass A" << endl; + if (DoRun() == false) + return false; + + if (Debug == true) + clog << "** Pass B" << endl; + Secondary = 0; + if (DoRun() == false) + return false; + + if (Debug == true) + clog << "** Pass C" << endl; + LoopCount = 0; + RevDepends = 0; + Remove = 0; // Otherwise the libreadline remove problem occurs + if (DoRun() == false) + return false; + + if (Debug == true) + clog << "** Pass D" << endl; + LoopCount = 0; + Primary = &pkgOrderList::DepUnPackPre; + if (DoRun() == false) + return false; + + if (Debug == true) + { + clog << "** Unpack ordering done" << endl; + + for (iterator I = List; I != End; ++I) + { + PkgIterator P(Cache,*I); + if (IsNow(P) == true) + clog << " " << P.FullName() << ' ' << IsMissing(P) << ',' << IsFlag(P,After) << endl; + } + } + + return true; +} + /*}}}*/ +// OrderList::OrderConfigure - Perform configuration ordering /*{{{*/ +// --------------------------------------------------------------------- +/* This orders by depends only and produces an order which is suitable + for configuration */ +bool pkgOrderList::OrderConfigure() +{ + FileList = 0; + Primary = &pkgOrderList::DepConfigure; + Secondary = 0; + RevDepends = 0; + Remove = 0; + LoopCount = -1; + return DoRun(); +} + /*}}}*/ +// OrderList::Score - Score the package for sorting /*{{{*/ +// --------------------------------------------------------------------- +/* Higher scores order earlier */ +int pkgOrderList::Score(PkgIterator Pkg) +{ + // Removals should be done after we dealt with essentials + static int const ScoreDelete = _config->FindI("OrderList::Score::Delete", 100); + if (Cache[Pkg].Delete() == true) + return ScoreDelete; + + // This should never happen.. + if (Cache[Pkg].InstVerIter(Cache).end() == true) + return -1; + + static int const ScoreEssential = _config->FindI("OrderList::Score::Essential", 200); + static int const ScoreImmediate = _config->FindI("OrderList::Score::Immediate", 10); + static int const ScorePreDepends = _config->FindI("OrderList::Score::PreDepends", 50); + + int Score = 0; + if ((Pkg->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential) + Score += ScoreEssential; + + if (IsFlag(Pkg,Immediate) == true) + Score += ScoreImmediate; + + for (DepIterator D = Cache[Pkg].InstVerIter(Cache).DependsList(); + D.end() == false; ++D) + if (D->Type == pkgCache::Dep::PreDepends) + { + Score += ScorePreDepends; + break; + } + + // Required Important Standard Optional Extra + if (Cache[Pkg].InstVerIter(Cache)->Priority <= 5) + { + signed short PrioMap[] = {0,5,4,3,1,0}; + Score += PrioMap[Cache[Pkg].InstVerIter(Cache)->Priority]; + } + return Score; +} + /*}}}*/ +// OrderList::FileCmp - Compare by package file /*{{{*/ +// --------------------------------------------------------------------- +/* This compares by the package file that the install version is in. */ +int pkgOrderList::FileCmp(PkgIterator A,PkgIterator B) +{ + if (Cache[A].Delete() == true && Cache[B].Delete() == true) + return 0; + if (Cache[A].Delete() == true) + return -1; + if (Cache[B].Delete() == true) + return 1; + + if (Cache[A].InstVerIter(Cache).FileList().end() == true) + return -1; + if (Cache[B].InstVerIter(Cache).FileList().end() == true) + return 1; + + pkgCache::PackageFile *FA = Cache[A].InstVerIter(Cache).FileList().File(); + pkgCache::PackageFile *FB = Cache[B].InstVerIter(Cache).FileList().File(); + if (FA < FB) + return -1; + if (FA > FB) + return 1; + return 0; +} + /*}}}*/ +// BoolCompare - Comparison function for two booleans /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static int BoolCompare(bool A,bool B) +{ + if (A == B) + return 0; + if (A == false) + return -1; + return 1; +} + /*}}}*/ +// OrderList::OrderCompareA - Order the installation by op /*{{{*/ +// --------------------------------------------------------------------- +/* This provides a first-pass sort of the list and gives a decent starting + point for further complete ordering. It is used by OrderUnpack only */ +int pkgOrderList::OrderCompareA(Package *a, Package *b) +{ + PkgIterator A(Cache,a); + PkgIterator B(Cache,b); + + // We order packages with a set state toward the front + int Res; + if ((Res = BoolCompare(IsNow(A),IsNow(B))) != 0) + return -1*Res; + + // We order missing files to toward the end +/* if (FileList != 0) + { + if ((Res = BoolCompare(IsMissing(A), + IsMissing(B))) != 0) + return Res; + }*/ + + if (A.State() != pkgCache::PkgIterator::NeedsNothing && + B.State() == pkgCache::PkgIterator::NeedsNothing) + return -1; + + if (A.State() == pkgCache::PkgIterator::NeedsNothing && + B.State() != pkgCache::PkgIterator::NeedsNothing) + return 1; + + int ScoreA = Score(A); + int ScoreB = Score(B); + + if (ScoreA > ScoreB) + return -1; + + if (ScoreA < ScoreB) + return 1; + + return strcmp(A.Name(),B.Name()); +} + /*}}}*/ +// OrderList::OrderCompareB - Order the installation by source /*{{{*/ +// --------------------------------------------------------------------- +/* This orders by installation source. This is useful to handle + inter-source breaks */ +int pkgOrderList::OrderCompareB(Package *a, Package *b) +{ + PkgIterator A(Cache,a); + PkgIterator B(Cache,b); + + if (A.State() != pkgCache::PkgIterator::NeedsNothing && + B.State() == pkgCache::PkgIterator::NeedsNothing) + return -1; + + if (A.State() == pkgCache::PkgIterator::NeedsNothing && + B.State() != pkgCache::PkgIterator::NeedsNothing) + return 1; + + int F = FileCmp(A,B); + if (F != 0) + { + if (F > 0) + return -1; + return 1; + } + + int ScoreA = Score(A); + int ScoreB = Score(B); + + if (ScoreA > ScoreB) + return -1; + + if (ScoreA < ScoreB) + return 1; + + return strcmp(A.Name(),B.Name()); +} + /*}}}*/ +// OrderList::VisitDeps - Visit forward install dependencies /*{{{*/ +// --------------------------------------------------------------------- +/* This calls the dependency function for the normal forwards dependencies + of the package */ +bool pkgOrderList::VisitDeps(DepFunc F,PkgIterator Pkg) +{ + if (F == 0 || Pkg.end() == true || Cache[Pkg].InstallVer == 0) + return true; + + return (this->*F)(Cache[Pkg].InstVerIter(Cache).DependsList()); +} + /*}}}*/ +// OrderList::VisitRDeps - Visit reverse dependencies /*{{{*/ +// --------------------------------------------------------------------- +/* This calls the dependency function for all of the normal reverse depends + of the package */ +bool pkgOrderList::VisitRDeps(DepFunc F,PkgIterator Pkg) +{ + if (F == 0 || Pkg.end() == true) + return true; + + return (this->*F)(Pkg.RevDependsList()); +} + /*}}}*/ +// OrderList::VisitRProvides - Visit provides reverse dependencies /*{{{*/ +// --------------------------------------------------------------------- +/* This calls the dependency function for all reverse dependencies + generated by the provides line on the package. */ +bool pkgOrderList::VisitRProvides(DepFunc F,VerIterator Ver) +{ + if (F == 0 || Ver.end() == true) + return true; + + bool Res = true; + for (PrvIterator P = Ver.ProvidesList(); P.end() == false; ++P) + Res &= (this->*F)(P.ParentPkg().RevDependsList()); + return Res; +} + /*}}}*/ +// OrderList::VisitProvides - Visit all of the providing packages /*{{{*/ +// --------------------------------------------------------------------- +/* This routine calls visit on all providing packages. + + If the dependency is negative it first visits packages which are + intended to be removed and after that all other packages. + It does so to avoid situations in which this package is used to + satisfy a (or-group/provides) dependency of another package which + could have been satisfied also by upgrading another package - + otherwise we have more broken packages dpkg needs to auto- + deconfigure and in very complicated situations it even decides + against it! */ +bool pkgOrderList::VisitProvides(DepIterator D,bool Critical) +{ + std::unique_ptr<Version *[]> List(D.AllTargets()); + for (Version **I = List.get(); *I != 0; ++I) + { + VerIterator Ver(Cache,*I); + PkgIterator Pkg = Ver.ParentPkg(); + + if (D.IsNegative() == true && Cache[Pkg].Delete() == false) + continue; + + if (Cache[Pkg].Keep() == true && Pkg.State() == PkgIterator::NeedsNothing) + continue; + + if (D.IsNegative() == false && + Cache[Pkg].InstallVer != *I) + continue; + + if (D.IsNegative() == true && + (Version *)Pkg.CurrentVer() != *I) + continue; + + // Skip over missing files + if (Critical == false && IsMissing(D.ParentPkg()) == true) + continue; + + if (VisitNode(Pkg, "Provides-1") == false) + return false; + } + if (D.IsNegative() == false) + return true; + for (Version **I = List.get(); *I != 0; ++I) + { + VerIterator Ver(Cache,*I); + PkgIterator Pkg = Ver.ParentPkg(); + + if (Cache[Pkg].Delete() == true) + continue; + + if (Cache[Pkg].Keep() == true && Pkg.State() == PkgIterator::NeedsNothing) + continue; + + if ((Version *)Pkg.CurrentVer() != *I) + continue; + + // Skip over missing files + if (Critical == false && IsMissing(D.ParentPkg()) == true) + continue; + + if (VisitNode(Pkg, "Provides-2") == false) + return false; + } + + return true; +} + /*}}}*/ +// OrderList::VisitNode - Recursive ordering director /*{{{*/ +// --------------------------------------------------------------------- +/* This is the core ordering routine. It calls the set dependency + consideration functions which then potentially call this again. Finite + depth is achieved through the colouring mechinism. */ +bool pkgOrderList::VisitNode(PkgIterator Pkg, char const* from) +{ + // Looping or irrelevant. + // This should probably trancend not installed packages + if (Pkg.end() == true || IsFlag(Pkg,Added) == true || + IsFlag(Pkg,AddPending) == true || IsFlag(Pkg,InList) == false) + return true; + + if (Debug == true) + { + for (int j = 0; j != Depth; j++) clog << ' '; + clog << "Visit " << Pkg.FullName() << " from " << from << endl; + } + + Depth++; + + // Color grey + Flag(Pkg,AddPending); + + DepFunc Old = Primary; + + // Perform immediate configuration of the package if so flagged. + if (IsFlag(Pkg,Immediate) == true && Primary != &pkgOrderList::DepUnPackPre) + Primary = &pkgOrderList::DepUnPackPreD; + + if (IsNow(Pkg) == true) + { + bool Res = true; + if (Cache[Pkg].Delete() == false) + { + // Primary + Res &= Res && VisitDeps(Primary,Pkg); + Res &= Res && VisitRDeps(Primary,Pkg); + Res &= Res && VisitRProvides(Primary,Pkg.CurrentVer()); + Res &= Res && VisitRProvides(Primary,Cache[Pkg].InstVerIter(Cache)); + + // RevDep + Res &= Res && VisitRDeps(RevDepends,Pkg); + Res &= Res && VisitRProvides(RevDepends,Pkg.CurrentVer()); + Res &= Res && VisitRProvides(RevDepends,Cache[Pkg].InstVerIter(Cache)); + + // Secondary + Res &= Res && VisitDeps(Secondary,Pkg); + Res &= Res && VisitRDeps(Secondary,Pkg); + Res &= Res && VisitRProvides(Secondary,Pkg.CurrentVer()); + Res &= Res && VisitRProvides(Secondary,Cache[Pkg].InstVerIter(Cache)); + } + else + { + // RevDep + Res &= Res && VisitRDeps(Remove,Pkg); + Res &= Res && VisitRProvides(Remove,Pkg.CurrentVer()); + } + } + + if (IsFlag(Pkg,Added) == false) + { + Flag(Pkg,Added,Added | AddPending); + if (IsFlag(Pkg,After) == true) + *AfterEnd++ = Pkg; + else + *End++ = Pkg; + } + + Primary = Old; + Depth--; + + if (Debug == true) + { + for (int j = 0; j != Depth; j++) clog << ' '; + clog << "Leave " << Pkg.FullName() << ' ' << IsFlag(Pkg,Added) << ',' << IsFlag(Pkg,AddPending) << endl; + } + + return true; +} + /*}}}*/ +// OrderList::DepUnPackCrit - Critical UnPacking ordering /*{{{*/ +// --------------------------------------------------------------------- +/* Critical unpacking ordering strives to satisfy Conflicts: and + PreDepends: only. When a prdepends is encountered the Primary + DepFunc is changed to be DepUnPackPreD. + + Loops are preprocessed and logged. */ +bool pkgOrderList::DepUnPackCrit(DepIterator D) +{ + for (; D.end() == false; ++D) + { + if (D.Reverse() == true) + { + /* Reverse depenanices are only interested in conflicts, + predepend breakage is ignored here */ + if (D->Type != pkgCache::Dep::Conflicts && + D->Type != pkgCache::Dep::Obsoletes) + continue; + + // Duplication elimination, consider only the current version + if (D.ParentPkg().CurrentVer() != D.ParentVer()) + continue; + + /* For reverse dependencies we wish to check if the + dependency is satisfied in the install state. The + target package (caller) is going to be in the installed + state. */ + if (CheckDep(D) == true) + continue; + + if (VisitNode(D.ParentPkg(), "UnPackCrit") == false) + return false; + } + else + { + /* Forward critical dependencies MUST be correct before the + package can be unpacked. */ + if (D.IsNegative() == false && + D->Type != pkgCache::Dep::PreDepends) + continue; + + /* We wish to check if the dep is okay in the now state of the + target package against the install state of this package. */ + if (CheckDep(D) == true) + { + /* We want to catch predepends loops with the code below. + Conflicts loops that are Dep OK are ignored */ + if (IsFlag(D.TargetPkg(),AddPending) == false || + D->Type != pkgCache::Dep::PreDepends) + continue; + } + + // This is the loop detection + if (IsFlag(D.TargetPkg(),Added) == true || + IsFlag(D.TargetPkg(),AddPending) == true) + { + if (IsFlag(D.TargetPkg(),AddPending) == true) + AddLoop(D); + continue; + } + + /* Predepends require a special ordering stage, they must have + all dependents installed as well */ + DepFunc Old = Primary; + bool Res = false; + if (D->Type == pkgCache::Dep::PreDepends) + Primary = &pkgOrderList::DepUnPackPreD; + Res = VisitProvides(D,true); + Primary = Old; + if (Res == false) + return false; + } + } + return true; +} + /*}}}*/ +// OrderList::DepUnPackPreD - Critical UnPacking ordering with depends /*{{{*/ +// --------------------------------------------------------------------- +/* Critical PreDepends (also configure immediate and essential) strives to + ensure not only that all conflicts+predepends are met but that this + package will be immediately configurable when it is unpacked. + Loops are preprocessed and logged. */ +bool pkgOrderList::DepUnPackPreD(DepIterator D) +{ + if (D.Reverse() == true) + return DepUnPackCrit(D); + + for (; D.end() == false; ++D) + { + if (D.IsCritical() == false) + continue; + + /* We wish to check if the dep is okay in the now state of the + target package against the install state of this package. */ + if (CheckDep(D) == true) + { + /* We want to catch predepends loops with the code below. + Conflicts loops that are Dep OK are ignored */ + if (IsFlag(D.TargetPkg(),AddPending) == false || + D->Type != pkgCache::Dep::PreDepends) + continue; + } + + // This is the loop detection + if (IsFlag(D.TargetPkg(),Added) == true || + IsFlag(D.TargetPkg(),AddPending) == true) + { + if (IsFlag(D.TargetPkg(),AddPending) == true) + AddLoop(D); + continue; + } + + if (VisitProvides(D,true) == false) + return false; + } + return true; +} + /*}}}*/ +// OrderList::DepUnPackPre - Critical Predepends ordering /*{{{*/ +// --------------------------------------------------------------------- +/* Critical PreDepends (also configure immediate and essential) strives to + ensure not only that all conflicts+predepends are met but that this + package will be immediately configurable when it is unpacked. + + Loops are preprocessed and logged. All loops will be fatal. */ +bool pkgOrderList::DepUnPackPre(DepIterator D) +{ + if (D.Reverse() == true) + return true; + + for (; D.end() == false; ++D) + { + /* Only consider the PreDepends or Depends. Depends are only + considered at the lowest depth or in the case of immediate + configure */ + if (D->Type != pkgCache::Dep::PreDepends) + { + if (D->Type == pkgCache::Dep::Depends) + { + if (Depth == 1 && IsFlag(D.ParentPkg(),Immediate) == false) + continue; + } + else + continue; + } + + /* We wish to check if the dep is okay in the now state of the + target package against the install state of this package. */ + if (CheckDep(D) == true) + { + /* We want to catch predepends loops with the code below. + Conflicts loops that are Dep OK are ignored */ + if (IsFlag(D.TargetPkg(),AddPending) == false) + continue; + } + + // This is the loop detection + if (IsFlag(D.TargetPkg(),Added) == true || + IsFlag(D.TargetPkg(),AddPending) == true) + { + if (IsFlag(D.TargetPkg(),AddPending) == true) + AddLoop(D); + continue; + } + + if (VisitProvides(D,true) == false) + return false; + } + return true; +} + /*}}}*/ +// OrderList::DepUnPackDep - Reverse dependency considerations /*{{{*/ +// --------------------------------------------------------------------- +/* Reverse dependencies are considered to determine if unpacking this + package will break any existing dependencies. If so then those + packages are ordered before this one so that they are in the + UnPacked state. + + The forwards depends loop is designed to bring the packages dependents + close to the package. This helps reduce deconfigure time. + + Loops are irrelevant to this. */ +bool pkgOrderList::DepUnPackDep(DepIterator D) +{ + + for (; D.end() == false; ++D) + if (D.IsCritical() == true) + { + if (D.Reverse() == true) + { + /* Duplication prevention. We consider rev deps only on + the current version, a not installed package + cannot break */ + if (D.ParentPkg()->CurrentVer == 0 || + D.ParentPkg().CurrentVer() != D.ParentVer()) + continue; + + // The dep will not break so it is irrelevant. + if (CheckDep(D) == true) + continue; + + // Skip over missing files + if (IsMissing(D.ParentPkg()) == true) + continue; + + if (VisitNode(D.ParentPkg(), "UnPackDep-Parent") == false) + return false; + } + else + { + if (D->Type == pkgCache::Dep::Depends) + if (VisitProvides(D,false) == false) + return false; + + if (D->Type == pkgCache::Dep::DpkgBreaks) + { + if (CheckDep(D) == true) + continue; + + if (VisitNode(D.TargetPkg(), "UnPackDep-Target") == false) + return false; + } + } + } + return true; +} + /*}}}*/ +// OrderList::DepConfigure - Configuration ordering /*{{{*/ +// --------------------------------------------------------------------- +/* Configuration only ordering orders by the Depends: line only. It + orders configuration so that when a package comes to be configured it's + dependents are configured. + + Loops are ignored. Depends loop entry points are chaotic. */ +bool pkgOrderList::DepConfigure(DepIterator D) +{ + // Never consider reverse configuration dependencies. + if (D.Reverse() == true) + return true; + + for (; D.end() == false; ++D) + if (D->Type == pkgCache::Dep::Depends) + if (VisitProvides(D,false) == false) + return false; + return true; +} + /*}}}*/ +// OrderList::DepRemove - Removal ordering /*{{{*/ +// --------------------------------------------------------------------- +/* Checks all given dependencies if they are broken by the removal of a + package and if so fix it by visiting another provider or or-group + member to ensure that the dependee keeps working which is especially + important for Immediate packages like e.g. those depending on an + awk implementation. If the dependency can't be fixed with another + package this means an upgrade of the package will solve the problem. */ +bool pkgOrderList::DepRemove(DepIterator Broken) +{ + if (Broken.Reverse() == false) + return true; + + for (; Broken.end() == false; ++Broken) + { + if (Broken->Type != pkgCache::Dep::Depends && + Broken->Type != pkgCache::Dep::PreDepends) + continue; + + PkgIterator BrokenPkg = Broken.ParentPkg(); + // uninstalled packages can't break via a remove + if (BrokenPkg->CurrentVer == 0) + continue; + + // if its already added, we can't do anything useful + if (IsFlag(BrokenPkg, AddPending) == true || IsFlag(BrokenPkg, Added) == true) + continue; + + // if the dependee is going to be removed, visit it now + if (Cache[BrokenPkg].Delete() == true) + return VisitNode(BrokenPkg, "Remove-Dependee"); + + // The package stays around, so find out how this is possible + for (DepIterator D = BrokenPkg.CurrentVer().DependsList(); D.end() == false;) + { + // only important or-groups need fixing + if (D->Type != pkgCache::Dep::Depends && + D->Type != pkgCache::Dep::PreDepends) + { + ++D; + continue; + } + + // Start is the beginning of the or-group, D is the first one after or + DepIterator Start = D; + bool foundBroken = false; + for (bool LastOR = true; D.end() == false && LastOR == true; ++D) + { + LastOR = (D->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or; + if (D == Broken) + foundBroken = true; + } + + // this or-group isn't the broken one: keep searching + if (foundBroken == false) + continue; + + // iterate over all members of the or-group searching for a ready replacement + bool readyReplacement = false; + for (DepIterator OrMember = Start; OrMember != D && readyReplacement == false; ++OrMember) + { + Version ** Replacements = OrMember.AllTargets(); + for (Version **R = Replacements; *R != 0; ++R) + { + VerIterator Ver(Cache,*R); + // only currently installed packages can be a replacement + PkgIterator RPkg = Ver.ParentPkg(); + if (RPkg.CurrentVer() != Ver) + continue; + + // packages going to be removed can't be a replacement + if (Cache[RPkg].Delete() == true) + continue; + + readyReplacement = true; + break; + } + delete[] Replacements; + } + + // something else is ready to take over, do nothing + if (readyReplacement == true) + continue; + + // see if we can visit a replacement + bool visitReplacement = false; + for (DepIterator OrMember = Start; OrMember != D && visitReplacement == false; ++OrMember) + { + Version ** Replacements = OrMember.AllTargets(); + for (Version **R = Replacements; *R != 0; ++R) + { + VerIterator Ver(Cache,*R); + // consider only versions we plan to install + PkgIterator RPkg = Ver.ParentPkg(); + if (Cache[RPkg].Install() == false || Cache[RPkg].InstallVer != Ver) + continue; + + // loops are not going to help us, so don't create them + if (IsFlag(RPkg, AddPending) == true) + continue; + + if (IsMissing(RPkg) == true) + continue; + + visitReplacement = true; + if (IsFlag(BrokenPkg, Immediate) == false) + { + if (VisitNode(RPkg, "Remove-Rep") == true) + break; + } + else + { + Flag(RPkg, Immediate); + if (VisitNode(RPkg, "Remove-ImmRep") == true) + break; + } + visitReplacement = false; + } + delete[] Replacements; + } + if (visitReplacement == true) + continue; + + // the broken package in current version can't be fixed, so install new version + if (IsMissing(BrokenPkg) == true) + break; + + if (VisitNode(BrokenPkg, "Remove-Upgrade") == false) + return false; + } + } + + return true; +} + /*}}}*/ +// OrderList::AddLoop - Add a loop to the loop list /*{{{*/ +// --------------------------------------------------------------------- +/* We record the loops. This is a relic since loop breaking is done + genericaly as part of the safety routines. */ +bool pkgOrderList::AddLoop(DepIterator D) +{ + if (LoopCount < 0 || LoopCount >= 20) + return false; + + // Skip dups + if (LoopCount != 0) + { + if (Loops[LoopCount - 1].ParentPkg() == D.ParentPkg() || + Loops[LoopCount - 1].TargetPkg() == D.ParentPkg()) + return true; + } + + Loops[LoopCount++] = D; + + // Mark the packages as being part of a loop. + //Flag(D.TargetPkg(),Loop); + //Flag(D.ParentPkg(),Loop); + /* This is currently disabled because the Loop flag is being used for + loop management in the package manager. Check the orderlist.h file for more info */ + return true; +} + /*}}}*/ +// OrderList::WipeFlags - Unset the given flags from all packages /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgOrderList::WipeFlags(unsigned long F) +{ + auto Size = Cache.Head().PackageCount; + for (decltype(Size) I = 0; I != Size; ++I) + Flags[I] &= ~F; +} + /*}}}*/ +// OrderList::CheckDep - Check a dependency for truth /*{{{*/ +// --------------------------------------------------------------------- +/* This performs a complete analysis of the dependency wrt to the + current add list. It returns true if after all events are + performed it is still true. This sort of routine can be approximated + by examining the DepCache, however in convoluted cases of provides + this fails to produce a suitable result. */ +bool pkgOrderList::CheckDep(DepIterator D) +{ + std::unique_ptr<Version *[]> List(D.AllTargets()); + bool Hit = false; + for (Version **I = List.get(); *I != 0; I++) + { + VerIterator Ver(Cache,*I); + PkgIterator Pkg = Ver.ParentPkg(); + + /* The meaning of Added and AddPending is subtle. AddPending is + an indication that the package is looping. Because of the + way ordering works Added means the package will be unpacked + before this one and AddPending means after. It is therefore + correct to ignore AddPending in all cases, but that exposes + reverse-ordering loops which should be ignored. */ + if (IsFlag(Pkg,Added) == true || + (IsFlag(Pkg,AddPending) == true && D.Reverse() == true)) + { + if (Cache[Pkg].InstallVer != *I) + continue; + } + else + if ((Version *)Pkg.CurrentVer() != *I || + Pkg.State() != PkgIterator::NeedsNothing) + continue; + + /* Conflicts requires that all versions are not present, depends + just needs one */ + if (D.IsNegative() == false) + { + // ignore provides by older versions of this package + if (((D.Reverse() == false && Pkg == D.ParentPkg()) || + (D.Reverse() == true && Pkg == D.TargetPkg())) && + Cache[Pkg].InstallVer != *I) + continue; + + /* Try to find something that does not have the after flag set + if at all possible */ + if (IsFlag(Pkg,After) == true) + { + Hit = true; + continue; + } + + return true; + } + else + { + if (IsFlag(Pkg,After) == true) + Flag(D.ParentPkg(),After); + + return false; + } + } + + // We found a hit, but it had the after flag set + if (Hit == true && D->Type == pkgCache::Dep::PreDepends) + { + Flag(D.ParentPkg(),After); + return true; + } + + /* Conflicts requires that all versions are not present, depends + just needs one */ + if (D->Type == pkgCache::Dep::Conflicts || + D->Type == pkgCache::Dep::Obsoletes) + return true; + return false; +} + /*}}}*/ diff --git a/apt-pkg/orderlist.h b/apt-pkg/orderlist.h new file mode 100644 index 0000000..3fa0234 --- /dev/null +++ b/apt-pkg/orderlist.h @@ -0,0 +1,127 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Order List - Represents and Manipulates an ordered list of packages. + + A list of packages can be ordered by a number of conflicting criteria + each given a specific priority. Each package also has a set of flags + indicating some useful things about it that are derived in the + course of sorting. The pkgPackageManager class uses this class for + all of it's installation ordering needs. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_ORDERLIST_H +#define PKGLIB_ORDERLIST_H + +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#include <string> + +class pkgDepCache; +class pkgOrderList : protected pkgCache::Namespace +{ + void * const d; + protected: + + pkgDepCache &Cache; + typedef bool (pkgOrderList::*DepFunc)(DepIterator D); + + // These are the currently selected ordering functions + DepFunc Primary; + DepFunc Secondary; + DepFunc RevDepends; + DepFunc Remove; + + // State + Package **End; + Package **List; + Package **AfterEnd; + std::string *FileList; + DepIterator Loops[20]; + int LoopCount; + int Depth; + unsigned short *Flags; + bool Debug; + + // Main visit function + APT_DEPRECATED_MSG("Add a unique calling identifier as parameter for debugging output") bool VisitNode(PkgIterator Pkg) { return VisitNode(Pkg, "UNKNOWN"); }; + bool VisitNode(PkgIterator Pkg, char const* from); + bool VisitDeps(DepFunc F,PkgIterator Pkg); + bool VisitRDeps(DepFunc F,PkgIterator Pkg); + bool VisitRProvides(DepFunc F,VerIterator Ver); + bool VisitProvides(DepIterator Pkg,bool Critical); + + // Dependency checking functions. + bool DepUnPackCrit(DepIterator D); + bool DepUnPackPreD(DepIterator D); + bool DepUnPackPre(DepIterator D); + bool DepUnPackDep(DepIterator D); + bool DepConfigure(DepIterator D); + bool DepRemove(DepIterator D); + + // Analysis helpers + bool AddLoop(DepIterator D); + bool CheckDep(DepIterator D); + bool DoRun(); + + // For pre sorting + int OrderCompareA(Package *a, Package *b) APT_PURE; + int OrderCompareB(Package *a, Package *b) APT_PURE; + int FileCmp(PkgIterator A,PkgIterator B) APT_PURE; + + public: + + typedef Package **iterator; + + /* State flags + The Loop flag can be set on a package that is currently being processed by either SmartConfigure or + SmartUnPack. This allows the package manager to tell when a loop has been formed as it will try to + SmartUnPack or SmartConfigure a package with the Loop flag set. It will then either stop (as it knows + that the operation is unnecessary as its already in process), or in the case of the conflicts resolution + in SmartUnPack, use EarlyRemove to resolve the situation. */ + enum Flags {Added = (1 << 0), AddPending = (1 << 1), + Immediate = (1 << 2), Loop = (1 << 3), + UnPacked = (1 << 4), Configured = (1 << 5), + Removed = (1 << 6), // Early Remove + InList = (1 << 7), + After = (1 << 8), + States = (UnPacked | Configured | Removed)}; + + // Flag manipulators + inline bool IsFlag(PkgIterator Pkg,unsigned long F) {return (Flags[Pkg->ID] & F) == F;}; + inline bool IsFlag(Package *Pkg,unsigned long F) {return (Flags[Pkg->ID] & F) == F;}; + void Flag(PkgIterator Pkg,unsigned long State, unsigned long F) {Flags[Pkg->ID] = (Flags[Pkg->ID] & (~F)) | State;}; + inline void Flag(PkgIterator Pkg,unsigned long F) {Flags[Pkg->ID] |= F;}; + inline void Flag(Package *Pkg,unsigned long F) {Flags[Pkg->ID] |= F;}; + // RmFlag removes a flag from a package + inline void RmFlag(Package *Pkg,unsigned long F) {Flags[Pkg->ID] &= ~F;}; + // IsNow will return true if the Pkg has been not been either configured or unpacked + inline bool IsNow(PkgIterator Pkg) {return (Flags[Pkg->ID] & (States & (~Removed))) == 0;}; + bool IsMissing(PkgIterator Pkg) APT_PURE; + void WipeFlags(unsigned long F); + void SetFileList(std::string *FileList) {this->FileList = FileList;}; + + // Accessors + inline iterator begin() {return List;}; + inline iterator end() {return End;}; + inline void push_back(Package *Pkg) {*(End++) = Pkg;}; + inline void push_back(PkgIterator Pkg) {*(End++) = Pkg;}; + inline void pop_back() {End--;}; + inline bool empty() {return End == List;}; + inline unsigned int size() {return End - List;}; + + // Ordering modes + bool OrderCritical(); + bool OrderUnpack(std::string *FileList = 0); + bool OrderConfigure(); + + int Score(PkgIterator Pkg); + + explicit pkgOrderList(pkgDepCache *Cache); + virtual ~pkgOrderList(); +}; + +#endif diff --git a/apt-pkg/packagemanager.cc b/apt-pkg/packagemanager.cc new file mode 100644 index 0000000..156f7ad --- /dev/null +++ b/apt-pkg/packagemanager.cc @@ -0,0 +1,1186 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Package Manager - Abstacts the package manager + + More work is needed in the area of transitioning provides, ie exim + replacing smail. This can cause interesting side effects. + + Other cases involving conflicts+replaces should be tested. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/acquire-item.h> +#include <apt-pkg/algorithms.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/edsp.h> +#include <apt-pkg/error.h> +#include <apt-pkg/install-progress.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/orderlist.h> +#include <apt-pkg/packagemanager.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/prettyprinters.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/version.h> + +#include <iostream> +#include <list> +#include <string> +#include <stddef.h> + +#include <apti18n.h> + /*}}}*/ +using namespace std; + +bool pkgPackageManager::SigINTStop = false; + +// PM::PackageManager - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgPackageManager::pkgPackageManager(pkgDepCache *pCache) : Cache(*pCache), + List(NULL), Res(Incomplete), d(NULL) +{ + FileNames = new string[Cache.Head().PackageCount]; + Debug = _config->FindB("Debug::pkgPackageManager",false); + NoImmConfigure = !_config->FindB("APT::Immediate-Configure",true); + ImmConfigureAll = _config->FindB("APT::Immediate-Configure-All",false); +} + /*}}}*/ +// PM::PackageManager - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgPackageManager::~pkgPackageManager() +{ + delete List; + delete [] FileNames; +} + /*}}}*/ +// PM::GetArchives - Queue the archives for download /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgPackageManager::GetArchives(pkgAcquire *Owner,pkgSourceList *Sources, + pkgRecords *Recs) +{ + if (CreateOrderList() == false) + return false; + + bool const ordering = + _config->FindB("PackageManager::UnpackAll",true) ? + List->OrderUnpack() : List->OrderCritical(); + if (ordering == false) + return _error->Error("Internal ordering error"); + + for (pkgOrderList::iterator I = List->begin(); I != List->end(); ++I) + { + PkgIterator Pkg(Cache,*I); + FileNames[Pkg->ID] = string(); + + // Skip packages to erase + if (Cache[Pkg].Delete() == true) + continue; + + // Skip Packages that need configure only. + if (Pkg.State() == pkgCache::PkgIterator::NeedsConfigure && + Cache[Pkg].Keep() == true) + continue; + + // Skip already processed packages + if (List->IsNow(Pkg) == false) + continue; + + new pkgAcqArchive(Owner,Sources,Recs,Cache[Pkg].InstVerIter(Cache), + FileNames[Pkg->ID]); + } + + return true; +} + /*}}}*/ +// PM::FixMissing - Keep all missing packages /*{{{*/ +// --------------------------------------------------------------------- +/* This is called to correct the installation when packages could not + be downloaded. */ +bool pkgPackageManager::FixMissing() +{ + pkgDepCache::ActionGroup group(Cache); + pkgProblemResolver Resolve(&Cache); + List->SetFileList(FileNames); + + bool Bad = false; + for (PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + if (List->IsMissing(I) == false) + continue; + + // Okay, this file is missing and we need it. Mark it for keep + Bad = true; + Cache.MarkKeep(I, false, false); + } + + // We have to empty the list otherwise it will not have the new changes + delete List; + List = 0; + + if (Bad == false) + return true; + + // Now downgrade everything that is broken + return Resolve.ResolveByKeep() == true && Cache.BrokenCount() == 0; +} + /*}}}*/ +// PM::ImmediateAdd - Add the immediate flag recursively /*{{{*/ +// --------------------------------------------------------------------- +/* This adds the immediate flag to the pkg and recursively to the + dependencies + */ +void pkgPackageManager::ImmediateAdd(PkgIterator I, bool UseInstallVer, unsigned const int &Depth) +{ + DepIterator D; + + if(UseInstallVer) + { + if(Cache[I].InstallVer == 0) + return; + D = Cache[I].InstVerIter(Cache).DependsList(); + } else { + if (I->CurrentVer == 0) + return; + D = I.CurrentVer().DependsList(); + } + + for ( /* nothing */ ; D.end() == false; ++D) + if (D->Type == pkgCache::Dep::Depends || D->Type == pkgCache::Dep::PreDepends) + { + if(!List->IsFlag(D.TargetPkg(), pkgOrderList::Immediate)) + { + if(Debug) + clog << OutputInDepth(Depth) << "ImmediateAdd(): Adding Immediate flag to " << APT::PrettyPkg(&Cache, D.TargetPkg()) << " cause of " << D.DepType() << " " << I.FullName() << endl; + List->Flag(D.TargetPkg(),pkgOrderList::Immediate); + ImmediateAdd(D.TargetPkg(), UseInstallVer, Depth + 1); + } + } + return; +} + /*}}}*/ +// PM::CreateOrderList - Create the ordering class /*{{{*/ +// --------------------------------------------------------------------- +/* This populates the ordering list with all the packages that are + going to change. */ +bool pkgPackageManager::CreateOrderList() +{ + if (List != 0) + return true; + + delete List; + List = new pkgOrderList(&Cache); + + if (Debug && ImmConfigureAll) + clog << "CreateOrderList(): Adding Immediate flag for all packages because of APT::Immediate-Configure-All" << endl; + + // Generate the list of affected packages and sort it + for (PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + // Ignore no-version packages + if (I->VersionList == 0) + continue; + + // Mark the package and its dependents for immediate configuration + if ((((I->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential) && + NoImmConfigure == false) || ImmConfigureAll) + { + if(Debug && !ImmConfigureAll) + clog << "CreateOrderList(): Adding Immediate flag for " << I.FullName() << endl; + List->Flag(I,pkgOrderList::Immediate); + + if (!ImmConfigureAll) { + // Look for other install packages to make immediate configurea + ImmediateAdd(I, true); + + // And again with the current version. + ImmediateAdd(I, false); + } + } + + // Not interesting + if ((Cache[I].Keep() == true || + Cache[I].InstVerIter(Cache) == I.CurrentVer()) && + I.State() == pkgCache::PkgIterator::NeedsNothing && + (Cache[I].iFlags & pkgDepCache::ReInstall) != pkgDepCache::ReInstall && + (I.Purge() != false || Cache[I].Mode != pkgDepCache::ModeDelete || + (Cache[I].iFlags & pkgDepCache::Purge) != pkgDepCache::Purge)) + continue; + + // Append it to the list + List->push_back(I); + } + + return true; +} + /*}}}*/ +// PM::DepAlwaysTrue - Returns true if this dep is irrelevant /*{{{*/ +// --------------------------------------------------------------------- +/* The restriction on provides is to eliminate the case when provides + are transitioning between valid states [ie exim to smail] */ +bool pkgPackageManager::DepAlwaysTrue(DepIterator D) +{ + if (D.TargetPkg()->ProvidesList != 0) + return false; + + if ((Cache[D] & pkgDepCache::DepInstall) != 0 && + (Cache[D] & pkgDepCache::DepNow) != 0) + return true; + return false; +} + /*}}}*/ +// PM::CheckRConflicts - Look for reverse conflicts /*{{{*/ +// --------------------------------------------------------------------- +/* This looks over the reverses for a conflicts line that needs early + removal. */ +bool pkgPackageManager::CheckRConflicts(PkgIterator Pkg,DepIterator D, + const char *Ver) +{ + for (;D.end() == false; ++D) + { + if (D->Type != pkgCache::Dep::Conflicts && + D->Type != pkgCache::Dep::Obsoletes) + continue; + + // The package hasn't been changed + if (List->IsNow(Pkg) == false) + continue; + + // Ignore self conflicts, ignore conflicts from irrelevant versions + if (D.IsIgnorable(Pkg) || D.ParentVer() != D.ParentPkg().CurrentVer()) + continue; + + if (Cache.VS().CheckDep(Ver,D->CompareOp,D.TargetVer()) == false) + continue; + + if (EarlyRemove(D.ParentPkg(), &D) == false) + return _error->Error("Reverse conflicts early remove for package '%s' failed", + Pkg.FullName().c_str()); + } + return true; +} + /*}}}*/ +// PM::CheckRBreaks - Look for reverse breaks /*{{{*/ +bool pkgPackageManager::CheckRBreaks(PkgIterator const &Pkg, DepIterator D, + const char * const Ver) +{ + for (;D.end() == false; ++D) + { + if (D->Type != pkgCache::Dep::DpkgBreaks) + continue; + + PkgIterator const DP = D.ParentPkg(); + if (Cache[DP].Delete() == false) + continue; + + // Ignore self conflicts, ignore conflicts from irrelevant versions + if (D.IsIgnorable(Pkg) || D.ParentVer() != DP.CurrentVer()) + continue; + + if (Cache.VS().CheckDep(Ver, D->CompareOp, D.TargetVer()) == false) + continue; + + // no earlyremove() here as user has already agreed to the permanent removal + if (SmartRemove(DP) == false) + return _error->Error("Internal Error, Could not early remove %s (%d)",DP.FullName().c_str(), 4); + } + return true; +} + /*}}}*/ +// PM::ConfigureAll - Run the all out configuration /*{{{*/ +// --------------------------------------------------------------------- +/* This configures every package. It is assumed they are all unpacked and + that the final configuration is valid. This is also used to catch packages + that have not been configured when using ImmConfigureAll */ +bool pkgPackageManager::ConfigureAll() +{ + pkgOrderList OList(&Cache); + + // Populate the order list + for (pkgOrderList::iterator I = List->begin(); I != List->end(); ++I) + if (List->IsFlag(pkgCache::PkgIterator(Cache,*I), + pkgOrderList::UnPacked) == true) + OList.push_back(*I); + + if (OList.OrderConfigure() == false) + return false; + + std::string const conf = _config->Find("PackageManager::Configure", "smart"); + bool const ConfigurePkgs = (ImmConfigureAll || conf == "all"); + + // Perform the configuring + for (pkgOrderList::iterator I = OList.begin(); I != OList.end(); ++I) + { + PkgIterator Pkg(Cache,*I); + + /* Check if the package has been configured, this can happen if SmartConfigure + calls its self */ + if (List->IsFlag(Pkg,pkgOrderList::Configured)) continue; + + if (ConfigurePkgs == true && SmartConfigure(Pkg, 0) == false) { + if (ImmConfigureAll) + _error->Error(_("Could not perform immediate configuration on '%s'. " + "Please see man 5 apt.conf under APT::Immediate-Configure for details. (%d)"),Pkg.FullName().c_str(),1); + else + _error->Error("Internal error, packages left unconfigured. %s",Pkg.FullName().c_str()); + return false; + } + + List->Flag(Pkg,pkgOrderList::Configured,pkgOrderList::States); + } + + return true; +} + /*}}}*/ +// PM::NonLoopingSmart - helper to avoid loops while calling Smart methods /*{{{*/ +// ----------------------------------------------------------------------- +/* ensures that a loop of the form A depends B, B depends A (and similar) + is not leading us down into infinite recursion segfault land */ +bool pkgPackageManager::NonLoopingSmart(SmartAction const action, pkgCache::PkgIterator &Pkg, + pkgCache::PkgIterator DepPkg, int const Depth, bool const PkgLoop, + bool * const Bad, bool * const Changed) +{ + if (PkgLoop == false) + List->Flag(Pkg,pkgOrderList::Loop); + bool success = false; + switch(action) + { + case UNPACK_IMMEDIATE: success = SmartUnPack(DepPkg, true, Depth + 1); break; + case UNPACK: success = SmartUnPack(DepPkg, false, Depth + 1); break; + case CONFIGURE: success = SmartConfigure(DepPkg, Depth + 1); break; + } + if (PkgLoop == false) + List->RmFlag(Pkg,pkgOrderList::Loop); + + if (success == false) + return false; + + if (Bad != NULL) + *Bad = false; + if (Changed != NULL && List->IsFlag(DepPkg,pkgOrderList::Loop) == false) + *Changed = true; + return true; +} + /*}}}*/ +// PM::SmartConfigure - Perform immediate configuration of the pkg /*{{{*/ +// --------------------------------------------------------------------- +/* This function tries to put the system in a state where Pkg can be configured. + This involves checking each of Pkg's dependencies and unpacking and + configuring packages where needed. */ +bool pkgPackageManager::SmartConfigure(PkgIterator Pkg, int const Depth) +{ + // If this is true, only check and correct and dependencies without the Loop flag + bool const PkgLoop = List->IsFlag(Pkg,pkgOrderList::Loop); + + if (Debug) { + VerIterator InstallVer = VerIterator(Cache,Cache[Pkg].InstallVer); + clog << OutputInDepth(Depth) << "SmartConfigure " << Pkg.FullName() << " (" << InstallVer.VerStr() << ")"; + if (PkgLoop) + clog << " (Only Correct Dependencies)"; + clog << endl; + } + + VerIterator const instVer = Cache[Pkg].InstVerIter(Cache); + + /* Because of the ordered list, most dependencies should be unpacked, + however if there is a loop (A depends on B, B depends on A) this will not + be the case, so check for dependencies before configuring. */ + bool Bad = false, Changed = false; + const unsigned int max_loops = _config->FindI("APT::pkgPackageManager::MaxLoopCount", 5000); + unsigned int i=0; + std::list<DepIterator> needConfigure; + do + { + // Check each dependency and see if anything needs to be done + // so that it can be configured + Changed = false; + for (DepIterator D = instVer.DependsList(); D.end() == false; ) + { + // Compute a single dependency element (glob or) + pkgCache::DepIterator Start, End; + D.GlobOr(Start,End); + + if (End->Type != pkgCache::Dep::Depends && End->Type != pkgCache::Dep::PreDepends) + continue; + Bad = true; + + // the first pass checks if we its all good, i.e. if we have + // to do anything at all + for (DepIterator Cur = Start; true; ++Cur) + { + std::unique_ptr<Version *[]> VList(Cur.AllTargets()); + + for (Version **I = VList.get(); *I != 0; ++I) + { + VerIterator Ver(Cache,*I); + PkgIterator DepPkg = Ver.ParentPkg(); + + // Check if the current version of the package is available and will satisfy this dependency + if (DepPkg.CurrentVer() == Ver && List->IsNow(DepPkg) == true && + List->IsFlag(DepPkg,pkgOrderList::Removed) == false && + DepPkg.State() == PkgIterator::NeedsNothing && + (Cache[DepPkg].iFlags & pkgDepCache::ReInstall) != pkgDepCache::ReInstall) + { + Bad = false; + break; + } + + // Check if the version that is going to be installed will satisfy the dependency + if (Cache[DepPkg].InstallVer != *I || List->IsNow(DepPkg) == false) + continue; + + if (PkgLoop == true) + { + if (Debug) + std::clog << OutputInDepth(Depth) << "Package " << APT::PrettyPkg(&Cache, Pkg) << " loops in SmartConfigure"; + if (List->IsFlag(DepPkg,pkgOrderList::UnPacked)) + Bad = false; + else if (Debug) + std::clog << ", but it isn't unpacked yet"; + if (Debug) + std::clog << std::endl; + } + } + + if (Cur == End || Bad == false) + break; + } + + // this dependency is in a good state, so we can stop + if (Bad == false) + { + if (Debug) + std::clog << OutputInDepth(Depth) << "Found ok dep " << APT::PrettyPkg(&Cache, Start.TargetPkg()) << std::endl; + continue; + } + + // Check for dependencies that have not been unpacked, + // probably due to loops. + for (DepIterator Cur = Start; true; ++Cur) + { + std::unique_ptr<Version *[]> VList(Cur.AllTargets()); + + for (Version **I = VList.get(); *I != 0; ++I) + { + VerIterator Ver(Cache,*I); + PkgIterator DepPkg = Ver.ParentPkg(); + + // Check if the current version of the package is available and will satisfy this dependency + if (DepPkg.CurrentVer() == Ver && List->IsNow(DepPkg) == true && + List->IsFlag(DepPkg,pkgOrderList::Removed) == false && + DepPkg.State() == PkgIterator::NeedsNothing && + (Cache[DepPkg].iFlags & pkgDepCache::ReInstall) != pkgDepCache::ReInstall) + continue; + + // Check if the version that is going to be installed will satisfy the dependency + if (Cache[DepPkg].InstallVer != *I || List->IsNow(DepPkg) == false) + continue; + + if (PkgLoop == true) + { + if (Debug) + std::clog << OutputInDepth(Depth) << "Package " << APT::PrettyPkg(&Cache, Pkg) << " loops in SmartConfigure"; + if (List->IsFlag(DepPkg,pkgOrderList::UnPacked)) + Bad = false; + else if (Debug) + std::clog << ", but it isn't unpacked yet"; + if (Debug) + std::clog << std::endl; + } + else + { + if (Debug) + clog << OutputInDepth(Depth) << "Unpacking " << DepPkg.FullName() << " to avoid loop " << APT::PrettyDep(&Cache, Cur) << endl; + if (NonLoopingSmart(UNPACK_IMMEDIATE, Pkg, DepPkg, Depth, PkgLoop, &Bad, &Changed) == false) + return false; + } + // at this point we either unpacked a Dep or we are in a loop, + // no need to unpack a second one + break; + } + + if (Cur == End || Bad == false) + break; + } + + if (Bad == false) + continue; + + needConfigure.push_back(Start); + } + if (i++ > max_loops) + return _error->Error("Internal error: MaxLoopCount reached in SmartUnPack (1) for %s, aborting", Pkg.FullName().c_str()); + } while (Changed == true); + + // now go over anything that needs configuring + Bad = false, Changed = false, i = 0; + do + { + Changed = false; + for (std::list<DepIterator>::const_iterator D = needConfigure.begin(); D != needConfigure.end(); ++D) + { + // Compute a single dependency element (glob or) without modifying D + pkgCache::DepIterator Start, End; + { + pkgCache::DepIterator Discard = *D; + Discard.GlobOr(Start,End); + } + + if (End->Type != pkgCache::Dep::Depends && End->Type != pkgCache::Dep::PreDepends) + continue; + Bad = true; + + // Search for dependencies which are unpacked but aren't configured yet (maybe loops) + for (DepIterator Cur = Start; true; ++Cur) + { + std::unique_ptr<Version *[]> VList(Cur.AllTargets()); + + for (Version **I = VList.get(); *I != 0; ++I) + { + VerIterator Ver(Cache,*I); + PkgIterator DepPkg = Ver.ParentPkg(); + + // Check if the version that is going to be installed will satisfy the dependency + if (Cache[DepPkg].InstallVer != *I) + continue; + + if (List->IsFlag(DepPkg,pkgOrderList::UnPacked)) + { + if (List->IsFlag(DepPkg,pkgOrderList::Loop) && PkgLoop) + { + // This dependency has already been dealt with by another SmartConfigure on Pkg + Bad = false; + break; + } + if (Debug) + std::clog << OutputInDepth(Depth) << "Configure already unpacked " << APT::PrettyPkg(&Cache, DepPkg) << std::endl; + if (NonLoopingSmart(CONFIGURE, Pkg, DepPkg, Depth, PkgLoop, &Bad, &Changed) == false) + return false; + break; + + } + else if (List->IsFlag(DepPkg,pkgOrderList::Configured)) + { + Bad = false; + break; + } + } + if (Cur == End || Bad == false) + break; + } + + + if (Bad == true && Changed == false && Debug == true) + std::clog << OutputInDepth(Depth) << "Could not satisfy " << APT::PrettyDep(&Cache, *D) << std::endl; + } + if (i++ > max_loops) + return _error->Error("Internal error: MaxLoopCount reached in SmartUnPack (2) for %s, aborting", Pkg.FullName().c_str()); + } while (Changed == true); + + if (Bad == true) + return _error->Error(_("Could not configure '%s'. "),Pkg.FullName().c_str()); + + // Check for reverse conflicts. + if (CheckRBreaks(Pkg,Pkg.RevDependsList(), instVer.VerStr()) == false) + return false; + + for (PrvIterator P = instVer.ProvidesList(); P.end() == false; ++P) + if (Pkg->Group != P.OwnerPkg()->Group) + CheckRBreaks(Pkg,P.ParentPkg().RevDependsList(),P.ProvideVersion()); + + if (PkgLoop) return true; + + static std::string const conf = _config->Find("PackageManager::Configure", "smart"); + static bool const ConfigurePkgs = (conf == "all" || conf == "smart"); + + if (List->IsFlag(Pkg,pkgOrderList::Configured)) + return _error->Error("Internal configure error on '%s'.", Pkg.FullName().c_str()); + + if (ConfigurePkgs == true && Configure(Pkg) == false) + return false; + + List->Flag(Pkg,pkgOrderList::Configured,pkgOrderList::States); + + if ((Cache[Pkg].InstVerIter(Cache)->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same) + for (PkgIterator P = Pkg.Group().PackageList(); + P.end() == false; P = Pkg.Group().NextPkg(P)) + { + if (Pkg == P || List->IsFlag(P,pkgOrderList::Configured) == true || + List->IsFlag(P,pkgOrderList::UnPacked) == false || + Cache[P].InstallVer == 0 || (P.CurrentVer() == Cache[P].InstallVer && + (Cache[Pkg].iFlags & pkgDepCache::ReInstall) != pkgDepCache::ReInstall)) + continue; + if (SmartConfigure(P, (Depth +1)) == false) + return false; + } + + // Sanity Check + if (List->IsFlag(Pkg,pkgOrderList::Configured) == false) + return _error->Error(_("Could not configure '%s'. "),Pkg.FullName().c_str()); + + return true; +} + /*}}}*/ +// PM::EarlyRemove - Perform removal of packages before their time /*{{{*/ +// --------------------------------------------------------------------- +/* This is called to deal with conflicts arising from unpacking */ +bool pkgPackageManager::EarlyRemove(PkgIterator Pkg) +{ + return EarlyRemove(Pkg, NULL); +} +bool pkgPackageManager::EarlyRemove(PkgIterator Pkg, DepIterator const * const Dep) +{ + if (List->IsNow(Pkg) == false) + return true; + + // Already removed it + if (List->IsFlag(Pkg,pkgOrderList::Removed) == true) + return true; + + // Woops, it will not be re-installed! + if (List->IsFlag(Pkg,pkgOrderList::InList) == false) + return false; + + // these breaks on M-A:same packages can be dealt with. They 'loop' by design + if (Dep != NULL && (*Dep)->Type == pkgCache::Dep::DpkgBreaks && Dep->IsMultiArchImplicit() == true) + return true; + + // Essential packages get special treatment + bool IsEssential = false; + if ((Pkg->Flags & pkgCache::Flag::Essential) != 0 || + (Pkg->Flags & pkgCache::Flag::Important) != 0) + IsEssential = true; + + /* Check for packages that are the dependents of essential packages and + promote them too */ + if (Pkg->CurrentVer != 0) + { + for (pkgCache::DepIterator D = Pkg.RevDependsList(); D.end() == false && + IsEssential == false; ++D) + if (D->Type == pkgCache::Dep::Depends || D->Type == pkgCache::Dep::PreDepends) + if ((D.ParentPkg()->Flags & pkgCache::Flag::Essential) != 0 || + (D.ParentPkg()->Flags & pkgCache::Flag::Important) != 0) + IsEssential = true; + } + + if (IsEssential == true) + { + if (_config->FindB("APT::Force-LoopBreak",false) == false) + return _error->Error(_("This installation run will require temporarily " + "removing the essential package %s due to a " + "Conflicts/Pre-Depends loop. This is often bad, " + "but if you really want to do it, activate the " + "APT::Force-LoopBreak option."),Pkg.FullName().c_str()); + } + // dpkg will auto-deconfigure it, no need for the big remove hammer + else if (Dep != NULL && (*Dep)->Type == pkgCache::Dep::DpkgBreaks) + return true; + + bool Res = SmartRemove(Pkg); + if (Cache[Pkg].Delete() == false) + List->Flag(Pkg,pkgOrderList::Removed,pkgOrderList::States); + + return Res; +} + /*}}}*/ +// PM::SmartRemove - Removal Helper /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgPackageManager::SmartRemove(PkgIterator Pkg) +{ + if (List->IsNow(Pkg) == false) + return true; + + List->Flag(Pkg,pkgOrderList::Configured,pkgOrderList::States); + + return Remove(Pkg,(Cache[Pkg].iFlags & pkgDepCache::Purge) == pkgDepCache::Purge); +} + /*}}}*/ +// PM::SmartUnPack - Install helper /*{{{*/ +// --------------------------------------------------------------------- +/* This puts the system in a state where it can Unpack Pkg, if Pkg is already + unpacked, or when it has been unpacked, if Immediate==true it configures it. */ +bool pkgPackageManager::SmartUnPack(PkgIterator Pkg) +{ + return SmartUnPack(Pkg, true, 0); +} +bool pkgPackageManager::SmartUnPack(PkgIterator Pkg, bool const Immediate, int const Depth) +{ + bool PkgLoop = List->IsFlag(Pkg,pkgOrderList::Loop); + + if (Debug) { + clog << OutputInDepth(Depth) << "SmartUnPack " << Pkg.FullName(); + VerIterator InstallVer = VerIterator(Cache,Cache[Pkg].InstallVer); + if (Pkg.CurrentVer() == 0) + clog << " (install version " << InstallVer.VerStr() << ")"; + else + clog << " (replace version " << Pkg.CurrentVer().VerStr() << " with " << InstallVer.VerStr() << ")"; + if (PkgLoop) + clog << " (Only Perform PreUnpack Checks)"; + if (Immediate) + clog << " immediately"; + clog << endl; + } + + VerIterator const instVer = Cache[Pkg].InstVerIter(Cache); + + /* PreUnpack Checks: This loop checks and attempts to rectify any problems that would prevent the package being unpacked. + It addresses: PreDepends, Conflicts, Obsoletes and Breaks (DpkgBreaks). Any resolutions that do not require it should + avoid configuration (calling SmartUnpack with Immediate=true), this is because when unpacking some packages with + complex dependency structures, trying to configure some packages while breaking the loops can complicate things. + This will be either dealt with if the package is configured as a dependency of Pkg (if and when Pkg is configured), + or by the ConfigureAll call at the end of the for loop in OrderInstall. */ + bool SomethingBad = false, Changed = false; + bool couldBeTemporaryRemoved = Depth != 0 && List->IsFlag(Pkg,pkgOrderList::Removed) == false; + const unsigned int max_loops = _config->FindI("APT::pkgPackageManager::MaxLoopCount", 5000); + unsigned int i = 0; + do + { + Changed = false; + for (DepIterator D = instVer.DependsList(); D.end() == false; ) + { + // Compute a single dependency element (glob or) + pkgCache::DepIterator Start, End; + D.GlobOr(Start,End); + + if (End->Type == pkgCache::Dep::PreDepends) + { + bool Bad = true; + if (Debug) + clog << OutputInDepth(Depth) << "PreDepends order for " << Pkg.FullName() << std::endl; + + // Look for easy targets: packages that are already okay + for (DepIterator Cur = Start; Bad == true; ++Cur) + { + std::unique_ptr<Version *[]> VList(Cur.AllTargets()); + for (Version **I = VList.get(); *I != 0; ++I) + { + VerIterator Ver(Cache,*I); + PkgIterator Pkg = Ver.ParentPkg(); + + // See if the current version is ok + if (Pkg.CurrentVer() == Ver && List->IsNow(Pkg) == true && + Pkg.State() == PkgIterator::NeedsNothing && + (Cache[Pkg].iFlags & pkgDepCache::ReInstall) != pkgDepCache::ReInstall) + { + Bad = false; + if (Debug) + clog << OutputInDepth(Depth) << "Found ok package " << Pkg.FullName() << endl; + break; + } + } + if (Cur == End) + break; + } + + // Look for something that could be configured. + for (DepIterator Cur = Start; Bad == true && Cur.end() == false; ++Cur) + { + std::unique_ptr<Version *[]> VList(Cur.AllTargets()); + for (Version **I = VList.get(); *I != 0; ++I) + { + VerIterator Ver(Cache,*I); + PkgIterator DepPkg = Ver.ParentPkg(); + + // Not the install version + if (Cache[DepPkg].InstallVer != *I) + continue; + + if (Cache[DepPkg].Keep() == true && DepPkg.State() == PkgIterator::NeedsNothing && + (Cache[DepPkg].iFlags & pkgDepCache::ReInstall) != pkgDepCache::ReInstall) + continue; + + if (List->IsFlag(DepPkg,pkgOrderList::Configured)) + { + Bad = false; + break; + } + + // check if it needs unpack or if configure is enough + if (List->IsFlag(DepPkg,pkgOrderList::UnPacked) == false) + { + // two packages pre-depending on each other can't be handled sanely + if (List->IsFlag(DepPkg,pkgOrderList::Loop) && PkgLoop) + { + // this isn't an error as there is potential for something else to satisfy it + // (like a provides or an or-group member) + if (Debug) + clog << OutputInDepth(Depth) << "Unpack loop detected between " << DepPkg.FullName() << " and " << Pkg.FullName() << endl; + continue; + } + + if (Debug) + clog << OutputInDepth(Depth) << "Trying to SmartUnpack " << DepPkg.FullName() << endl; + if (NonLoopingSmart(UNPACK_IMMEDIATE, Pkg, DepPkg, Depth, PkgLoop, &Bad, &Changed) == false) + return false; + } + else + { + if (Debug) + clog << OutputInDepth(Depth) << "Trying to SmartConfigure " << DepPkg.FullName() << endl; + if (NonLoopingSmart(CONFIGURE, Pkg, DepPkg, Depth, PkgLoop, &Bad, &Changed) == false) + return false; + } + break; + } + } + + if (Bad == true) + SomethingBad = true; + } + else if (End->Type == pkgCache::Dep::Conflicts || + End->Type == pkgCache::Dep::Obsoletes || + End->Type == pkgCache::Dep::DpkgBreaks) + { + std::unique_ptr<Version *[]> VList(End.AllTargets()); + for (Version **I = VList.get(); *I != 0; ++I) + { + VerIterator Ver(Cache,*I); + PkgIterator ConflictPkg = Ver.ParentPkg(); + if (ConflictPkg.CurrentVer() != Ver) + { + if (Debug) + std::clog << OutputInDepth(Depth) << "Ignore not-installed version " << Ver.VerStr() << " of " << ConflictPkg.FullName() << " for " << APT::PrettyDep(&Cache, End) << std::endl; + continue; + } + + if (List->IsNow(ConflictPkg) == false) + { + if (Debug) + std::clog << OutputInDepth(Depth) << "Ignore already dealt-with version " << Ver.VerStr() << " of " << ConflictPkg.FullName() << " for " << APT::PrettyDep(&Cache, End) << std::endl; + continue; + } + + if (List->IsFlag(ConflictPkg,pkgOrderList::Removed) == true) + { + if (Debug) + clog << OutputInDepth(Depth) << "Ignoring " << APT::PrettyDep(&Cache, End) << " as " << ConflictPkg.FullName() << "was temporarily removed" << endl; + continue; + } + + if (List->IsFlag(ConflictPkg,pkgOrderList::Loop) && PkgLoop) + { + if (End->Type == pkgCache::Dep::DpkgBreaks && End.IsMultiArchImplicit() == true) + { + if (Debug) + clog << OutputInDepth(Depth) << "Because dependency is MultiArchImplicit we ignored looping on: " << APT::PrettyPkg(&Cache, ConflictPkg) << endl; + continue; + } + if (Debug) + { + if (End->Type == pkgCache::Dep::DpkgBreaks) + clog << OutputInDepth(Depth) << "Because of breaks knot, deconfigure " << ConflictPkg.FullName() << " temporarily" << endl; + else + clog << OutputInDepth(Depth) << "Because of conflict knot, removing " << ConflictPkg.FullName() << " temporarily" << endl; + } + if (EarlyRemove(ConflictPkg, &End) == false) + return _error->Error("Internal Error, Could not early remove %s (%d)",ConflictPkg.FullName().c_str(), 3); + SomethingBad = true; + continue; + } + + if (Cache[ConflictPkg].Delete() == false) + { + if (Debug) + { + clog << OutputInDepth(Depth) << "Unpacking " << ConflictPkg.FullName() << " to avoid " << APT::PrettyDep(&Cache, End); + if (PkgLoop == true) + clog << " (Looping)"; + clog << std::endl; + } + // we would like to avoid temporary removals and all that at best via a simple unpack + _error->PushToStack(); + if (NonLoopingSmart(UNPACK, Pkg, ConflictPkg, Depth, PkgLoop, NULL, &Changed) == false) + { + // but if it fails ignore this failure and look for alternative ways of solving + if (Debug) + { + clog << OutputInDepth(Depth) << "Avoidance unpack of " << ConflictPkg.FullName() << " failed for " << APT::PrettyDep(&Cache, End) << " ignoring:" << std::endl; + _error->DumpErrors(std::clog, GlobalError::DEBUG, false); + } + _error->RevertToStack(); + // ignorance can only happen if a) one of the offenders is already gone + if (List->IsFlag(ConflictPkg,pkgOrderList::Removed) == true) + { + if (Debug) + clog << OutputInDepth(Depth) << "But " << ConflictPkg.FullName() << " was temporarily removed in the meantime to satisfy " << APT::PrettyDep(&Cache, End) << endl; + } + else if (List->IsFlag(Pkg,pkgOrderList::Removed) == true) + { + if (Debug) + clog << OutputInDepth(Depth) << "But " << Pkg.FullName() << " was temporarily removed in the meantime to satisfy " << APT::PrettyDep(&Cache, End) << endl; + } + // or b) we can make one go (removal or dpkg auto-deconfigure) + else + { + if (Debug) + clog << OutputInDepth(Depth) << "So temprorary remove/deconfigure " << ConflictPkg.FullName() << " to satisfy " << APT::PrettyDep(&Cache, End) << endl; + if (EarlyRemove(ConflictPkg, &End) == false) + return _error->Error("Internal Error, Could not early remove %s (%d)",ConflictPkg.FullName().c_str(), 2); + } + } + else + _error->MergeWithStack(); + } + else + { + if (Debug) + clog << OutputInDepth(Depth) << "Removing " << ConflictPkg.FullName() << " now to avoid " << APT::PrettyDep(&Cache, End) << endl; + // no earlyremove() here as user has already agreed to the permanent removal + if (SmartRemove(ConflictPkg) == false) + return _error->Error("Internal Error, Could not early remove %s (%d)",ConflictPkg.FullName().c_str(), 1); + } + } + } + } + if (i++ > max_loops) + return _error->Error("Internal error: APT::pkgPackageManager::MaxLoopCount reached in SmartConfigure for %s, aborting", Pkg.FullName().c_str()); + } while (Changed == true); + + if (SomethingBad == true) + return _error->Error("Couldn't configure %s, probably a dependency cycle.", Pkg.FullName().c_str()); + + if (couldBeTemporaryRemoved == true && List->IsFlag(Pkg,pkgOrderList::Removed) == true) + { + if (Debug) + std::clog << OutputInDepth(Depth) << "Prevent unpack as " << APT::PrettyPkg(&Cache, Pkg) << " is currently temporarily removed" << std::endl; + return true; + } + + // Check for reverse conflicts. + if (CheckRConflicts(Pkg,Pkg.RevDependsList(), + instVer.VerStr()) == false) + return false; + + for (PrvIterator P = instVer.ProvidesList(); + P.end() == false; ++P) + if (Pkg->Group != P.OwnerPkg()->Group) + CheckRConflicts(Pkg,P.ParentPkg().RevDependsList(),P.ProvideVersion()); + + if (PkgLoop) + return true; + + List->Flag(Pkg,pkgOrderList::UnPacked,pkgOrderList::States); + + if (Immediate == true && (instVer->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same) + { + /* Do lockstep M-A:same unpacking in two phases: + First unpack all installed architectures, then the not installed. + This way we avoid that M-A: enabled packages are installed before + their older non-M-A enabled packages are replaced by newer versions */ + bool const installed = Pkg->CurrentVer != 0; + if (installed == true && + (instVer != Pkg.CurrentVer() || + ((Cache[Pkg].iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall)) && + Install(Pkg,FileNames[Pkg->ID]) == false) + return false; + for (PkgIterator P = Pkg.Group().PackageList(); + P.end() == false; P = Pkg.Group().NextPkg(P)) + { + if (P->CurrentVer == 0 || P == Pkg || List->IsFlag(P,pkgOrderList::UnPacked) == true || + Cache[P].InstallVer == 0 || (P.CurrentVer() == Cache[P].InstallVer && + (Cache[Pkg].iFlags & pkgDepCache::ReInstall) != pkgDepCache::ReInstall)) + continue; + if (SmartUnPack(P, false, Depth + 1) == false) + return false; + } + if (installed == false && Install(Pkg,FileNames[Pkg->ID]) == false) + return false; + for (PkgIterator P = Pkg.Group().PackageList(); + P.end() == false; P = Pkg.Group().NextPkg(P)) + { + if (P->CurrentVer != 0 || P == Pkg || List->IsFlag(P,pkgOrderList::UnPacked) == true || + List->IsFlag(P,pkgOrderList::Configured) == true || + Cache[P].InstallVer == 0 || (P.CurrentVer() == Cache[P].InstallVer && + (Cache[Pkg].iFlags & pkgDepCache::ReInstall) != pkgDepCache::ReInstall)) + continue; + if (SmartUnPack(P, false, Depth + 1) == false) + return false; + } + } + // packages which are already unpacked don't need to be unpacked again + else if ((instVer != Pkg.CurrentVer() || + ((Cache[Pkg].iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall)) && + Install(Pkg,FileNames[Pkg->ID]) == false) + return false; + + if (Immediate == true) { + // Perform immediate configuration of the package. + if (SmartConfigure(Pkg, Depth + 1) == false) + _error->Error(_("Could not perform immediate configuration on '%s'. " + "Please see man 5 apt.conf under APT::Immediate-Configure for details. (%d)"),Pkg.FullName().c_str(),2); + } + + return true; +} + /*}}}*/ +// PM::OrderInstall - Installation ordering routine /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgPackageManager::OrderResult pkgPackageManager::OrderInstall() +{ + if (CreateOrderList() == false) + return Failed; + + Reset(); + + if (Debug == true) + clog << "Beginning to order" << endl; + + std::string const planner = _config->Find("APT::Planner", "internal"); + unsigned int flags = 0; + if (_config->FindB("APT::Immediate-Configure", true) == false) + flags |= EIPP::Request::NO_IMMEDIATE_CONFIGURATION; + else if (_config->FindB("APT::Immediate-Configure-All", false)) + flags |= EIPP::Request::IMMEDIATE_CONFIGURATION_ALL; + else if (_config->FindB("APT::Force-LoopBreak", false)) + flags |= EIPP::Request::ALLOW_TEMPORARY_REMOVE_OF_ESSENTIALS; + auto const ret = EIPP::OrderInstall(planner.c_str(), this, flags, nullptr); + if (planner != "internal") + return ret ? Completed : Failed; + + bool const ordering = + _config->FindB("PackageManager::UnpackAll",true) ? + List->OrderUnpack(FileNames) : List->OrderCritical(); + if (ordering == false) + { + _error->Error("Internal ordering error"); + return Failed; + } + + if (Debug == true) + clog << "Done ordering" << endl; + + bool DoneSomething = false; + for (pkgOrderList::iterator I = List->begin(); I != List->end(); ++I) + { + PkgIterator Pkg(Cache,*I); + + if (List->IsNow(Pkg) == false) + { + if (Debug == true) + clog << "Skipping already done " << Pkg.FullName() << endl; + continue; + } + + if (List->IsMissing(Pkg) == true) + { + if (Debug == true) + clog << "Sequence completed at " << Pkg.FullName() << endl; + if (DoneSomething == false) + { + _error->Error("Internal Error, ordering was unable to handle the media swap"); + return Failed; + } + return Incomplete; + } + + // Sanity check + if (Cache[Pkg].Keep() == true && + Pkg.State() == pkgCache::PkgIterator::NeedsNothing && + (Cache[Pkg].iFlags & pkgDepCache::ReInstall) != pkgDepCache::ReInstall) + { + _error->Error("Internal Error, trying to manipulate a kept package (%s)",Pkg.FullName().c_str()); + return Failed; + } + + // Perform a delete or an install + if (Cache[Pkg].Delete() == true) + { + if (SmartRemove(Pkg) == false) + return Failed; + } + else + if (SmartUnPack(Pkg,List->IsFlag(Pkg,pkgOrderList::Immediate),0) == false) + return Failed; + DoneSomething = true; + + if (ImmConfigureAll) { + /* ConfigureAll here to pick up and packages left unconfigured because they were unpacked in the + "PreUnpack Checks" section */ + if (!ConfigureAll()) + return Failed; + } + } + + // Final run through the configure phase + if (ConfigureAll() == false) + return Failed; + + // Sanity check + for (pkgOrderList::iterator I = List->begin(); I != List->end(); ++I) + { + if (List->IsFlag(*I,pkgOrderList::Configured) == false) + { + _error->Error("Internal error, packages left unconfigured. %s", + PkgIterator(Cache,*I).FullName().c_str()); + return Failed; + } + } + + return Completed; +} +// PM::DoInstallPostFork - compat /*{{{*/ +// --------------------------------------------------------------------- + /*}}}*/ +pkgPackageManager::OrderResult +pkgPackageManager::DoInstallPostFork(int statusFd) +{ + APT::Progress::PackageManager *progress = new + APT::Progress::PackageManagerProgressFd(statusFd); + pkgPackageManager::OrderResult res = DoInstallPostFork(progress); + delete progress; + return res; +} + /*}}}*/ +// PM::DoInstallPostFork - Does install part that happens after the fork /*{{{*/ +// --------------------------------------------------------------------- +pkgPackageManager::OrderResult +pkgPackageManager::DoInstallPostFork(APT::Progress::PackageManager *progress) +{ + bool goResult; + auto simulation = dynamic_cast<pkgSimulate*>(this); + if (simulation == nullptr) + goResult = Go(progress); + else + goResult = simulation->Go2(progress); + if(goResult == false) + return Failed; + + return Res; +} + /*}}}*/ +// PM::DoInstall - Does the installation /*{{{*/ +// --------------------------------------------------------------------- +/* compat */ +pkgPackageManager::OrderResult +pkgPackageManager::DoInstall(int statusFd) +{ + APT::Progress::PackageManager *progress = new + APT::Progress::PackageManagerProgressFd(statusFd); + OrderResult res = DoInstall(progress); + delete progress; + return res; + } + /*}}}*/ +// PM::DoInstall - Does the installation /*{{{*/ +// --------------------------------------------------------------------- +/* This uses the filenames in FileNames and the information in the + DepCache to perform the installation of packages.*/ +pkgPackageManager::OrderResult +pkgPackageManager::DoInstall(APT::Progress::PackageManager *progress) +{ + if(DoInstallPreFork() == Failed) + return Failed; + + return DoInstallPostFork(progress); +} + /*}}}*/ diff --git a/apt-pkg/packagemanager.h b/apt-pkg/packagemanager.h new file mode 100644 index 0000000..d944344 --- /dev/null +++ b/apt-pkg/packagemanager.h @@ -0,0 +1,153 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Package Manager - Abstacts the package manager + + Three steps are + - Aquiration of archives (stores the list of final file names) + - Sorting of operations + - Invocation of package manager + + This is the final stage when the package cache entities get converted + into file names and the state stored in a DepCache is transformed + into a series of operations. + + In the final scheme of things this may serve as a director class to + access the actual install methods based on the file type being + installed. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_PACKAGEMANAGER_H +#define PKGLIB_PACKAGEMANAGER_H + +#include <apt-pkg/edsp.h> +#include <apt-pkg/init.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#include <set> +#include <string> + +#ifndef APT_10_CLEANER_HEADERS +#include <apt-pkg/install-progress.h> +#include <iostream> +#endif +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/depcache.h> +using std::string; +#endif + +class pkgAcquire; +class pkgDepCache; +class pkgSourceList; +class pkgOrderList; +class pkgRecords; +class OpProgress; +class pkgPackageManager; +namespace APT { + namespace Progress { + class PackageManager; + } +} + +class pkgPackageManager : protected pkgCache::Namespace +{ + public: + + enum OrderResult {Completed,Failed,Incomplete}; + static bool SigINTStop; + + protected: + std::string *FileNames; + pkgDepCache &Cache; + pkgOrderList *List; + bool Debug; + bool NoImmConfigure; + bool ImmConfigureAll; + + /** \brief saves packages dpkg let disappear + + This way APT can retreat from trying to configure these + packages later on and a front-end can choose to display a + notice to inform the user about these disappears. + */ + std::set<std::string> disappearedPkgs; + + void ImmediateAdd(PkgIterator P, bool UseInstallVer, unsigned const int &Depth = 0); + virtual OrderResult OrderInstall(); + bool CheckRConflicts(PkgIterator Pkg,DepIterator Dep,const char *Ver); + bool CheckRBreaks(PkgIterator const &Pkg,DepIterator Dep,const char * const Ver); + bool CreateOrderList(); + + // Analysis helpers + bool DepAlwaysTrue(DepIterator D) APT_PURE; + + // Install helpers + bool ConfigureAll(); + bool SmartConfigure(PkgIterator Pkg, int const Depth) APT_MUSTCHECK; + //FIXME: merge on abi break + bool SmartUnPack(PkgIterator Pkg) APT_MUSTCHECK; + bool SmartUnPack(PkgIterator Pkg, bool const Immediate, int const Depth) APT_MUSTCHECK; + bool SmartRemove(PkgIterator Pkg) APT_MUSTCHECK; + bool EarlyRemove(PkgIterator Pkg, DepIterator const * const Dep) APT_MUSTCHECK; + APT_DEPRECATED bool EarlyRemove(PkgIterator Pkg) APT_MUSTCHECK; + + // The Actual installation implementation + virtual bool Install(PkgIterator /*Pkg*/,std::string /*File*/) {return false;}; + virtual bool Configure(PkgIterator /*Pkg*/) {return false;}; + virtual bool Remove(PkgIterator /*Pkg*/,bool /*Purge*/=false) {return false;}; + virtual bool Go(APT::Progress::PackageManager * /*progress*/) {return true;}; + APT_DEPRECATED_MSG("Use overload with explicit progress manager") virtual bool Go(int /*statusFd*/=-1) {return true;}; + + virtual void Reset() {}; + + // the result of the operation + OrderResult Res; + + public: + + // Main action members + bool GetArchives(pkgAcquire *Owner,pkgSourceList *Sources, + pkgRecords *Recs); + + // Do the installation + OrderResult DoInstall(APT::Progress::PackageManager *progress); + // compat + APT_DEPRECATED_MSG("Use APT::Progress::PackageManager subclass instead of fd") OrderResult DoInstall(int statusFd=-1); + + friend bool EIPP::OrderInstall(char const * const planner, pkgPackageManager * const PM, + unsigned int const version, OpProgress * const Progress); + friend bool EIPP::ReadResponse(int const input, pkgPackageManager * const PM, + OpProgress * const Progress); + + // stuff that needs to be done before the fork() of a library that + // uses apt + OrderResult DoInstallPreFork() { + Res = OrderInstall(); + return Res; + }; + // stuff that needs to be done after the fork + OrderResult DoInstallPostFork(APT::Progress::PackageManager *progress); + // compat + APT_DEPRECATED_MSG("Use APT::Progress::PackageManager subclass instead of fd") OrderResult DoInstallPostFork(int statusFd=-1); + + // ? + bool FixMissing(); + + /** \brief returns all packages dpkg let disappear */ + inline std::set<std::string> GetDisappearedPackages() { return disappearedPkgs; }; + + explicit pkgPackageManager(pkgDepCache *Cache); + virtual ~pkgPackageManager(); + + private: + void * const d; + enum APT_HIDDEN SmartAction { UNPACK_IMMEDIATE, UNPACK, CONFIGURE }; + APT_HIDDEN bool NonLoopingSmart(SmartAction const action, pkgCache::PkgIterator &Pkg, + pkgCache::PkgIterator DepPkg, int const Depth, bool const PkgLoop, + bool * const Bad, bool * const Changed) APT_MUSTCHECK; +}; + +#endif diff --git a/apt-pkg/pkgcache.cc b/apt-pkg/pkgcache.cc new file mode 100644 index 0000000..80dd1d1 --- /dev/null +++ b/apt-pkg/pkgcache.cc @@ -0,0 +1,1108 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Package Cache - Accessor code for the cache + + Please see doc/apt-pkg/cache.sgml for a more detailed description of + this format. Also be sure to keep that file up-to-date!! + + This is the general utility functions for cache management. They provide + a complete set of accessor functions for the cache. The cacheiterators + header contains the STL-like iterators that can be used to easially + navigate the cache as well as seamlessly dereference the mmap'd + indexes. Use these always. + + The main class provides for ways to get package indexes and some + general lookup functions to start the iterators. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/aptconfiguration.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/mmap.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/policy.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/version.h> + +#include <zlib.h> +#include <algorithm> +#include <sstream> +#include <string> +#include <vector> +#include <stddef.h> +#include <string.h> +#include <sys/stat.h> + +#include <apti18n.h> + /*}}}*/ + +using std::string; +using APT::StringView; + + +// Cache::Header::Header - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* Simply initialize the header */ +pkgCache::Header::Header() +{ +#define APT_HEADER_SET(X,Y) X = Y; static_assert(std::numeric_limits<decltype(X)>::max() > Y, "Size violation detected in pkgCache::Header") + APT_HEADER_SET(Signature, 0x98FE76DC); + + /* Whenever the structures change the major version should be bumped, + whenever the generator changes the minor version should be bumped. */ + APT_HEADER_SET(MajorVersion, 13); + APT_HEADER_SET(MinorVersion, 0); + APT_HEADER_SET(Dirty, false); + + APT_HEADER_SET(HeaderSz, sizeof(pkgCache::Header)); + APT_HEADER_SET(GroupSz, sizeof(pkgCache::Group)); + APT_HEADER_SET(PackageSz, sizeof(pkgCache::Package)); + APT_HEADER_SET(ReleaseFileSz, sizeof(pkgCache::ReleaseFile)); + APT_HEADER_SET(PackageFileSz, sizeof(pkgCache::PackageFile)); + APT_HEADER_SET(VersionSz, sizeof(pkgCache::Version)); + APT_HEADER_SET(DescriptionSz, sizeof(pkgCache::Description)); + APT_HEADER_SET(DependencySz, sizeof(pkgCache::Dependency)); + APT_HEADER_SET(DependencyDataSz, sizeof(pkgCache::DependencyData)); + APT_HEADER_SET(ProvidesSz, sizeof(pkgCache::Provides)); + APT_HEADER_SET(VerFileSz, sizeof(pkgCache::VerFile)); + APT_HEADER_SET(DescFileSz, sizeof(pkgCache::DescFile)); +#undef APT_HEADER_SET + + GroupCount = 0; + PackageCount = 0; + VersionCount = 0; + DescriptionCount = 0; + DependsCount = 0; + DependsDataCount = 0; + ReleaseFileCount = 0; + PackageFileCount = 0; + VerFileCount = 0; + DescFileCount = 0; + ProvidesCount = 0; + MaxVerFileSize = 0; + MaxDescFileSize = 0; + + FileList = 0; + RlsFileList = 0; + VerSysName = 0; + Architecture = 0; + SetArchitectures(0); + SetHashTableSize(_config->FindI("APT::Cache-HashTableSize", 50503)); + memset(Pools,0,sizeof(Pools)); + + CacheFileSize = 0; +} + /*}}}*/ +// Cache::Header::CheckSizes - Check if the two headers have same *sz /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgCache::Header::CheckSizes(Header &Against) const +{ + if (HeaderSz == Against.HeaderSz && + GroupSz == Against.GroupSz && + PackageSz == Against.PackageSz && + ReleaseFileSz == Against.ReleaseFileSz && + PackageFileSz == Against.PackageFileSz && + VersionSz == Against.VersionSz && + DescriptionSz == Against.DescriptionSz && + DependencySz == Against.DependencySz && + DependencyDataSz == Against.DependencyDataSz && + VerFileSz == Against.VerFileSz && + DescFileSz == Against.DescFileSz && + ProvidesSz == Against.ProvidesSz) + return true; + return false; +} + /*}}}*/ + +// Cache::pkgCache - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +APT_IGNORE_DEPRECATED_PUSH +pkgCache::pkgCache(MMap *Map, bool DoMap) : Map(*Map), VS(nullptr), d(NULL) +{ + // call getArchitectures() with cached=false to ensure that the + // architectures cache is re-evaulated. this is needed in cases + // when the APT::Architecture field changes between two cache creations + MultiArchEnabled = APT::Configuration::getArchitectures(false).size() > 1; + if (DoMap == true) + ReMap(); +} +APT_IGNORE_DEPRECATED_POP + /*}}}*/ +// Cache::ReMap - Reopen the cache file /*{{{*/ +// --------------------------------------------------------------------- +/* If the file is already closed then this will open it open it. */ +bool pkgCache::ReMap(bool const &Errorchecks) +{ + // Apply the typecasts. + HeaderP = (Header *)Map.Data(); + GrpP = (Group *)Map.Data(); + PkgP = (Package *)Map.Data(); + VerFileP = (VerFile *)Map.Data(); + DescFileP = (DescFile *)Map.Data(); + RlsFileP = (ReleaseFile *)Map.Data(); + PkgFileP = (PackageFile *)Map.Data(); + VerP = (Version *)Map.Data(); + DescP = (Description *)Map.Data(); + ProvideP = (Provides *)Map.Data(); + DepP = (Dependency *)Map.Data(); + DepDataP = (DependencyData *)Map.Data(); + StrP = (char *)Map.Data(); + + if (Errorchecks == false) + return true; + + if (Map.Size() == 0 || HeaderP == 0) + return _error->Error(_("Empty package cache")); + + // Check the header + Header DefHeader; + if (HeaderP->Signature != DefHeader.Signature || + HeaderP->Dirty == true) + return _error->Error(_("The package cache file is corrupted")); + + if (HeaderP->MajorVersion != DefHeader.MajorVersion || + HeaderP->MinorVersion != DefHeader.MinorVersion || + HeaderP->CheckSizes(DefHeader) == false) + return _error->Error(_("The package cache file is an incompatible version")); + + if (HeaderP->VerSysName == 0 || HeaderP->Architecture == 0 || HeaderP->GetArchitectures() == 0) + return _error->Error(_("The package cache file is corrupted")); + + // Locate our VS.. + if ((VS = pkgVersioningSystem::GetVS(StrP + HeaderP->VerSysName)) == 0) + return _error->Error(_("This APT does not support the versioning system '%s'"),StrP + HeaderP->VerSysName); + + // Check the architecture + std::vector<std::string> archs = APT::Configuration::getArchitectures(); + std::string list = ""; + for (auto const & arch : archs) { + if (!list.empty()) + list.append(","); + list.append(arch); + } + if (_config->Find("APT::Architecture") != StrP + HeaderP->Architecture || + list != StrP + HeaderP->GetArchitectures()) + return _error->Error(_("The package cache was built for different architectures: %s vs %s"), StrP + HeaderP->GetArchitectures(), list.c_str()); + + + auto hash = CacheHash(); + if (_config->FindB("Debug::pkgCacheGen", false)) + std::clog << "Opened cache with hash " << hash << ", expecting " << HeaderP->CacheFileSize << "\n"; + if (hash != HeaderP->CacheFileSize) + return _error->Error(_("The package cache file is corrupted, it has the wrong hash")); + + return true; +} + /*}}}*/ +// Cache::Hash - Hash a string /*{{{*/ +// --------------------------------------------------------------------- +/* This is used to generate the hash entries for the HashTable. With my + package list from bo this function gets 94% table usage on a 512 item + table (480 used items) */ +map_id_t pkgCache::sHash(StringView Str) const +{ + uint32_t Hash = 5381; + for (auto I = Str.begin(); I != Str.end(); ++I) + Hash = 33 * Hash + tolower_ascii_unsafe(*I); + return Hash % HeaderP->GetHashTableSize(); +} +map_id_t pkgCache::sHash(const string &Str) const +{ + uint32_t Hash = 5381; + for (string::const_iterator I = Str.begin(); I != Str.end(); ++I) + Hash = 33 * Hash + tolower_ascii_unsafe((signed char)*I); + return Hash % HeaderP->GetHashTableSize(); +} + +map_id_t pkgCache::sHash(const char *Str) const +{ + uint32_t Hash = 5381; + for (const char *I = Str; *I != 0; ++I) + Hash = 33 * Hash + tolower_ascii_unsafe((signed char)*I); + return Hash % HeaderP->GetHashTableSize(); +} + +#if defined(HAVE_FMV_SSE42_AND_CRC32) + +#ifdef HAVE_FMV_SSE42_AND_CRC32 +__attribute__((target("sse4.2"))) static uint32_t hash32(uint32_t crc32, const unsigned char *input, size_t size) +{ + if (input == nullptr) + return 0; + + crc32 ^= 0xffffffffU; +#ifdef HAVE_FMV_SSE42_AND_CRC32DI + while (size >= 8) { + crc32 = __builtin_ia32_crc32di(crc32, *(uint64_t *)input); + input += 8; + size -= 8; + } + + if (size >= 4) { +#else + while (size >= 4) { +#endif + crc32 = __builtin_ia32_crc32si(crc32, *(uint32_t *)input); + input += 4; + size -= 4; + } + + if (size >= 2) { + crc32 = __builtin_ia32_crc32hi(crc32, *(uint16_t *)input); + input += 2; + size -= 2; + } + + if (size >= 1) { + crc32 = __builtin_ia32_crc32qi(crc32, *(uint8_t *)input); + input += 1; + size -= 1; + } + crc32 ^= 0xffffffffU; + return crc32; +} +#endif + +__attribute__((target("default"))) +#endif +static uint32_t hash32(uint32_t crc32, const unsigned char *input, size_t size) +{ + return adler32(crc32, input, size); +} + +uint32_t pkgCache::CacheHash() +{ + pkgCache::Header header = {}; + uLong adler = hash32(0L, Z_NULL, 0); + + if (Map.Size() < sizeof(header)) + return adler; + memcpy(&header, GetMap().Data(), sizeof(header)); + + header.Dirty = false; + header.CacheFileSize = 0; + + adler = hash32(adler, + reinterpret_cast<const unsigned char *>(&header), + sizeof(header)); + + if (Map.Size() > sizeof(header)) { + adler = hash32(adler, + static_cast<const unsigned char *>(GetMap().Data()) + sizeof(header), + GetMap().Size() - sizeof(header)); + } + + return adler; +} + /*}}}*/ +// Cache::FindPkg - Locate a package by name /*{{{*/ +// --------------------------------------------------------------------- +/* Returns 0 on error, pointer to the package otherwise */ +pkgCache::PkgIterator pkgCache::FindPkg(const string &Name) { + return FindPkg(StringView(Name)); +} + +pkgCache::PkgIterator pkgCache::FindPkg(StringView Name) { + auto const found = Name.rfind(':'); + if (found == string::npos) + return FindPkg(Name, "native"); + auto const Arch = Name.substr(found+1); + /* Beware: This is specialcased to handle pkg:any in dependencies + as these are linked to virtual pkg:any named packages. + If you want any arch from a pkg, use FindPkg(pkg,"any") */ + if (Arch == "any") + return FindPkg(Name, "any"); + return FindPkg(Name.substr(0, found), Arch); +} + /*}}}*/ +// Cache::FindPkg - Locate a package by name /*{{{*/ +// --------------------------------------------------------------------- +/* Returns 0 on error, pointer to the package otherwise */ +pkgCache::PkgIterator pkgCache::FindPkg(const string &Name, string const &Arch) { + return FindPkg(StringView(Name), StringView(Arch)); +} + +pkgCache::PkgIterator pkgCache::FindPkg(StringView Name, StringView Arch) { + /* We make a detour via the GrpIterator here as + on a multi-arch environment a group is easier to + find than a package (less entries in the buckets) */ + pkgCache::GrpIterator Grp = FindGrp(Name); + if (Grp.end() == true) + return PkgIterator(*this,0); + + return Grp.FindPkg(Arch); +} + /*}}}*/ +// Cache::FindGrp - Locate a group by name /*{{{*/ +// --------------------------------------------------------------------- +/* Returns End-Pointer on error, pointer to the group otherwise */ +pkgCache::GrpIterator pkgCache::FindGrp(const string &Name) { + return FindGrp(StringView(Name)); +} + +pkgCache::GrpIterator pkgCache::FindGrp(StringView Name) { + if (unlikely(Name.empty() == true)) + return GrpIterator(*this,0); + + // Look at the hash bucket for the group + Group *Grp = GrpP + HeaderP->GrpHashTableP()[sHash(Name)]; + for (; Grp != GrpP; Grp = GrpP + Grp->Next) { + int const cmp = StringViewCompareFast(Name, ViewString(Grp->Name)); + if (cmp == 0) + return GrpIterator(*this, Grp); + else if (cmp < 0) + break; + } + + return GrpIterator(*this,0); +} + /*}}}*/ +// Cache::CompTypeDeb - Return a string describing the compare type /*{{{*/ +// --------------------------------------------------------------------- +/* This returns a string representation of the dependency compare + type in the weird debian style.. */ +const char *pkgCache::CompTypeDeb(unsigned char Comp) +{ + const char * const Ops[] = {"","<=",">=","<<",">>","=","!="}; + if (unlikely((unsigned)(Comp & 0xF) >= sizeof(Ops)/sizeof(Ops[0]))) + return ""; + return Ops[Comp & 0xF]; +} + /*}}}*/ +// Cache::CompType - Return a string describing the compare type /*{{{*/ +// --------------------------------------------------------------------- +/* This returns a string representation of the dependency compare + type */ +const char *pkgCache::CompType(unsigned char Comp) +{ + const char * const Ops[] = {"","<=",">=","<",">","=","!="}; + if (unlikely((unsigned)(Comp & 0xF) >= sizeof(Ops)/sizeof(Ops[0]))) + return ""; + return Ops[Comp & 0xF]; +} + /*}}}*/ +// Cache::DepType - Return a string describing the dep type /*{{{*/ +// --------------------------------------------------------------------- +/* */ +const char *pkgCache::DepType(unsigned char Type) +{ + const char *Types[] = {"",_("Depends"),_("PreDepends"),_("Suggests"), + _("Recommends"),_("Conflicts"),_("Replaces"), + _("Obsoletes"),_("Breaks"), _("Enhances")}; + if (Type < sizeof(Types)/sizeof(*Types)) + return Types[Type]; + return ""; +} + /*}}}*/ +// Cache::Priority - Convert a priority value to a string /*{{{*/ +// --------------------------------------------------------------------- +/* */ +const char *pkgCache::Priority(unsigned char Prio) +{ + const char *Mapping[] = {0,_("required"),_("important"),_("standard"), + _("optional"),_("extra")}; + if (Prio < _count(Mapping)) + return Mapping[Prio]; + return 0; +} + /*}}}*/ +// GrpIterator::FindPkg - Locate a package by arch /*{{{*/ +// --------------------------------------------------------------------- +/* Returns an End-Pointer on error, pointer to the package otherwise */ +pkgCache::PkgIterator pkgCache::GrpIterator::FindPkg(string Arch) const { + return FindPkg(StringView(Arch)); +} +pkgCache::PkgIterator pkgCache::GrpIterator::FindPkg(const char *Arch) const { + return FindPkg(StringView(Arch)); +} +pkgCache::PkgIterator pkgCache::GrpIterator::FindPkg(StringView Arch) const { + if (unlikely(IsGood() == false || S->FirstPackage == 0)) + return PkgIterator(*Owner, 0); + + /* If we accept any package we simply return the "first" + package in this group */ + if (Arch == "any") + return PkgIterator(*Owner, Owner->PkgP + S->FirstPackage); + if (Arch == "native" || Arch == "all") + Arch = Owner->NativeArch(); + + // Iterate over the list to find the matching arch + for (pkgCache::Package *Pkg = PackageList(); Pkg != Owner->PkgP; + Pkg = Owner->PkgP + Pkg->NextPackage) { + if (Arch == Owner->ViewString(Pkg->Arch)) + return PkgIterator(*Owner, Pkg); + if ((Owner->PkgP + S->LastPackage) == Pkg) + break; + } + + return PkgIterator(*Owner, 0); +} + /*}}}*/ +// GrpIterator::FindPreferredPkg - Locate the "best" package /*{{{*/ +// --------------------------------------------------------------------- +/* Returns an End-Pointer on error, pointer to the package otherwise */ +pkgCache::PkgIterator pkgCache::GrpIterator::FindPreferredPkg(bool const &PreferNonVirtual) const { + pkgCache::PkgIterator Pkg = FindPkg(StringView("native", 6)); + if (Pkg.end() == false && (PreferNonVirtual == false || Pkg->VersionList != 0)) + return Pkg; + + std::vector<std::string> const archs = APT::Configuration::getArchitectures(); + for (std::vector<std::string>::const_iterator a = archs.begin(); + a != archs.end(); ++a) { + Pkg = FindPkg(*a); + if (Pkg.end() == false && (PreferNonVirtual == false || Pkg->VersionList != 0)) + return Pkg; + } + // packages without an architecture + Pkg = FindPkg(StringView("none", 4)); + if (Pkg.end() == false && (PreferNonVirtual == false || Pkg->VersionList != 0)) + return Pkg; + + if (PreferNonVirtual == true) + return FindPreferredPkg(false); + return PkgIterator(*Owner, 0); +} + /*}}}*/ +// GrpIterator::NextPkg - Locate the next package in the group /*{{{*/ +// --------------------------------------------------------------------- +/* Returns an End-Pointer on error, pointer to the package otherwise. + We can't simply ++ to the next as the next package of the last will + be from a different group (with the same hash value) */ +pkgCache::PkgIterator pkgCache::GrpIterator::NextPkg(pkgCache::PkgIterator const &LastPkg) const { + if (unlikely(IsGood() == false || S->FirstPackage == 0 || + LastPkg.end() == true)) + return PkgIterator(*Owner, 0); + + if (S->LastPackage == LastPkg.Index()) + return PkgIterator(*Owner, 0); + + return PkgIterator(*Owner, Owner->PkgP + LastPkg->NextPackage); +} + /*}}}*/ +// GrpIterator::operator++ - Prefix incr /*{{{*/ +// --------------------------------------------------------------------- +/* This will advance to the next logical group in the hash table. */ +pkgCache::GrpIterator& pkgCache::GrpIterator::operator++() +{ + // Follow the current links + if (S != Owner->GrpP) + S = Owner->GrpP + S->Next; + + // Follow the hash table + while (S == Owner->GrpP && (HashIndex+1) < (signed)Owner->HeaderP->GetHashTableSize()) + { + ++HashIndex; + S = Owner->GrpP + Owner->HeaderP->GrpHashTableP()[HashIndex]; + } + return *this; +} + /*}}}*/ +// PkgIterator::operator++ - Prefix incr /*{{{*/ +// --------------------------------------------------------------------- +/* This will advance to the next logical package in the hash table. */ +pkgCache::PkgIterator& pkgCache::PkgIterator::operator++() +{ + // Follow the current links + if (S != Owner->PkgP) + S = Owner->PkgP + S->NextPackage; + + // Follow the hash table + while (S == Owner->PkgP && (HashIndex+1) < (signed)Owner->HeaderP->GetHashTableSize()) + { + ++HashIndex; + S = Owner->PkgP + Owner->HeaderP->PkgHashTableP()[HashIndex]; + } + return *this; +} + /*}}}*/ +pkgCache::DepIterator& pkgCache::DepIterator::operator++() /*{{{*/ +{ + if (S == Owner->DepP) + return *this; + S = Owner->DepP + (Type == DepVer ? S->NextDepends : S->NextRevDepends); + if (S == Owner->DepP) + S2 = Owner->DepDataP; + else + S2 = Owner->DepDataP + S->DependencyData; + return *this; +} + /*}}}*/ +// PkgIterator::State - Check the State of the package /*{{{*/ +// --------------------------------------------------------------------- +/* By this we mean if it is either cleanly installed or cleanly removed. */ +pkgCache::PkgIterator::OkState pkgCache::PkgIterator::State() const +{ + if (S->InstState == pkgCache::State::ReInstReq || + S->InstState == pkgCache::State::HoldReInstReq) + return NeedsUnpack; + + if (S->CurrentState == pkgCache::State::UnPacked || + S->CurrentState == pkgCache::State::HalfConfigured) + // we leave triggers alone completely. dpkg deals with + // them in a hard-to-predict manner and if they get + // resolved by dpkg before apt run dpkg --configure on + // the TriggersPending package dpkg returns a error + //Pkg->CurrentState == pkgCache::State::TriggersAwaited + //Pkg->CurrentState == pkgCache::State::TriggersPending) + return NeedsConfigure; + + if (S->CurrentState == pkgCache::State::HalfInstalled || + S->InstState != pkgCache::State::Ok) + return NeedsUnpack; + + return NeedsNothing; +} + /*}}}*/ +// PkgIterator::CandVersion - Returns the candidate version string /*{{{*/ +// --------------------------------------------------------------------- +/* Return string representing of the candidate version. */ +const char * +pkgCache::PkgIterator::CandVersion() const +{ + //TargetVer is empty, so don't use it. + VerIterator version = pkgPolicy(Owner).GetCandidateVer(*this); + if (version.IsGood()) + return version.VerStr(); + return 0; +} + /*}}}*/ +// PkgIterator::CurVersion - Returns the current version string /*{{{*/ +// --------------------------------------------------------------------- +/* Return string representing of the current version. */ +const char * +pkgCache::PkgIterator::CurVersion() const +{ + VerIterator version = CurrentVer(); + if (version.IsGood()) + return CurrentVer().VerStr(); + return 0; +} + /*}}}*/ +// ostream operator to handle string representation of a package /*{{{*/ +// --------------------------------------------------------------------- +/* Output name < cur.rent.version -> candid.ate.version | new.est.version > (section) + Note that the characters <|>() are all literal above. Versions will be omitted + if they provide no new information (e.g. there is no newer version than candidate) + If no version and/or section can be found "none" is used. */ +std::ostream& +operator<<(std::ostream& out, pkgCache::PkgIterator Pkg) +{ + if (Pkg.end() == true) + return out << "invalid package"; + + string current = string(Pkg.CurVersion() == 0 ? "none" : Pkg.CurVersion()); +APT_IGNORE_DEPRECATED_PUSH + string candidate = string(Pkg.CandVersion() == 0 ? "none" : Pkg.CandVersion()); +APT_IGNORE_DEPRECATED_POP + string newest = string(Pkg.VersionList().end() ? "none" : Pkg.VersionList().VerStr()); + + out << Pkg.Name() << " [ " << Pkg.Arch() << " ] < " << current; + if (current != candidate) + out << " -> " << candidate; + if ( newest != "none" && candidate != newest) + out << " | " << newest; + if (Pkg->VersionList == 0) + out << " > ( none )"; + else + out << " > ( " << string(Pkg.VersionList().Section()==0?"unknown":Pkg.VersionList().Section()) << " )"; + return out; +} + /*}}}*/ +// PkgIterator::FullName - Returns Name and (maybe) Architecture /*{{{*/ +// --------------------------------------------------------------------- +/* Returns a name:arch string */ +std::string pkgCache::PkgIterator::FullName(bool const &Pretty) const +{ + string fullname = Name(); + if (Pretty == false || + (strcmp(Arch(), "all") != 0 && strcmp(Arch(), "any") != 0 && + strcmp(Owner->NativeArch(), Arch()) != 0)) + return fullname.append(":").append(Arch()); + return fullname; +} + /*}}}*/ +// DepIterator::IsCritical - Returns true if the dep is important /*{{{*/ +// --------------------------------------------------------------------- +/* Currently critical deps are defined as depends, predepends and + conflicts (including dpkg's Breaks fields). */ +bool pkgCache::DepIterator::IsCritical() const +{ + if (IsNegative() == true || + S2->Type == pkgCache::Dep::Depends || + S2->Type == pkgCache::Dep::PreDepends) + return true; + return false; +} + /*}}}*/ +// DepIterator::IsNegative - Returns true if the dep is a negative one /*{{{*/ +// --------------------------------------------------------------------- +/* Some dependencies are positive like Depends and Recommends, others + are negative like Conflicts which can and should be handled differently */ +bool pkgCache::DepIterator::IsNegative() const +{ + return S2->Type == Dep::DpkgBreaks || + S2->Type == Dep::Conflicts || + S2->Type == Dep::Obsoletes; +} + /*}}}*/ +// DepIterator::SmartTargetPkg - Resolve dep target pointers w/provides /*{{{*/ +// --------------------------------------------------------------------- +/* This intellegently looks at dep target packages and tries to figure + out which package should be used. This is needed to nicely handle + provide mapping. If the target package has no other providing packages + then it returned. Otherwise the providing list is looked at to + see if there is one unique providing package if so it is returned. + Otherwise true is returned and the target package is set. The return + result indicates whether the node should be expandable + + In Conjunction with the DepCache the value of Result may not be + super-good since the policy may have made it uninstallable. Using + AllTargets is better in this case. */ +bool pkgCache::DepIterator::SmartTargetPkg(PkgIterator &Result) const +{ + Result = TargetPkg(); + + // No provides at all + if (Result->ProvidesList == 0) + return false; + + // There is the Base package and the providing ones which is at least 2 + if (Result->VersionList != 0) + return true; + + /* We have to skip over indirect provisions of the package that + owns the dependency. For instance, if libc5-dev depends on the + virtual package libc-dev which is provided by libc5-dev and libc6-dev + we must ignore libc5-dev when considering the provides list. */ + PrvIterator PStart = Result.ProvidesList(); + for (; PStart.end() != true && PStart.OwnerPkg() == ParentPkg(); ++PStart); + + // Nothing but indirect self provides + if (PStart.end() == true) + return false; + + // Check for single packages in the provides list + PrvIterator P = PStart; + for (; P.end() != true; ++P) + { + // Skip over self provides + if (P.OwnerPkg() == ParentPkg()) + continue; + if (PStart.OwnerPkg() != P.OwnerPkg()) + break; + } + + Result = PStart.OwnerPkg(); + + // Check for non dups + if (P.end() != true) + return true; + + return false; +} + /*}}}*/ +// DepIterator::AllTargets - Returns the set of all possible targets /*{{{*/ +// --------------------------------------------------------------------- +/* This is a more useful version of TargetPkg() that follows versioned + provides. It includes every possible package-version that could satisfy + the dependency. The last item in the list has a 0. The resulting pointer + must be delete [] 'd */ +pkgCache::Version **pkgCache::DepIterator::AllTargets() const +{ + Version **Res = 0; + unsigned long Size =0; + while (1) + { + Version **End = Res; + PkgIterator DPkg = TargetPkg(); + + // Walk along the actual package providing versions + for (VerIterator I = DPkg.VersionList(); I.end() == false; ++I) + { + if (IsIgnorable(I.ParentPkg()) == true) + continue; + if (IsSatisfied(I) == false) + continue; + + Size++; + if (Res != 0) + *End++ = I; + } + + // Follow all provides + for (PrvIterator I = DPkg.ProvidesList(); I.end() == false; ++I) + { + if (IsIgnorable(I) == true) + continue; + if (IsSatisfied(I) == false) + continue; + + Size++; + if (Res != 0) + *End++ = I.OwnerVer(); + } + + // Do it again and write it into the array + if (Res == 0) + { + Res = new Version *[Size+1]; + Size = 0; + } + else + { + *End = 0; + break; + } + } + + return Res; +} + /*}}}*/ +// DepIterator::GlobOr - Compute an OR group /*{{{*/ +// --------------------------------------------------------------------- +/* This Takes an iterator, iterates past the current dependency grouping + and returns Start and End so that so End is the final element + in the group, if End == Start then D is End++ and End is the + dependency D was pointing to. Use in loops to iterate sensibly. */ +void pkgCache::DepIterator::GlobOr(DepIterator &Start,DepIterator &End) +{ + // Compute a single dependency element (glob or) + Start = *this; + End = *this; + for (bool LastOR = true; end() == false && LastOR == true;) + { + LastOR = (S2->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or; + ++(*this); + if (LastOR == true) + End = (*this); + } +} + /*}}}*/ +// DepIterator::IsIgnorable - should this packag/providr be ignored? /*{{{*/ +// --------------------------------------------------------------------- +/* Deps like self-conflicts should be ignored as well as implicit conflicts + on virtual packages. */ +bool pkgCache::DepIterator::IsIgnorable(PkgIterator const &PT) const +{ + if (IsNegative() == false) + return false; + + pkgCache::PkgIterator const PP = ParentPkg(); + if (PP->Group != PT->Group) + return false; + // self-conflict + if (PP == PT) + return true; + pkgCache::VerIterator const PV = ParentVer(); + // ignore group-conflict on a M-A:same package - but not our implicit dependencies + // so that we can have M-A:same packages conflicting with their own real name + if ((PV->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same) + return IsMultiArchImplicit() == false; + + return false; +} +bool pkgCache::DepIterator::IsIgnorable(PrvIterator const &Prv) const +{ + if (IsNegative() == false) + return false; + + PkgIterator const Pkg = ParentPkg(); + /* Provides may never be applied against the same package (or group) + if it is a conflicts. See the comment above. */ + if (Prv.OwnerPkg()->Group == Pkg->Group) + return true; + // Implicit group-conflicts should not be applied on providers of other groups + if (IsMultiArchImplicit() && Prv.OwnerPkg()->Group != Pkg->Group) + return true; + + return false; +} + /*}}}*/ +// DepIterator::IsSatisfied - check if a version satisfied the dependency /*{{{*/ +bool pkgCache::DepIterator::IsSatisfied(VerIterator const &Ver) const +{ + return Owner->VS->CheckDep(Ver.VerStr(),S2->CompareOp,TargetVer()); +} +bool pkgCache::DepIterator::IsSatisfied(PrvIterator const &Prv) const +{ + return Owner->VS->CheckDep(Prv.ProvideVersion(),S2->CompareOp,TargetVer()); +} + /*}}}*/ +// DepIterator::IsImplicit - added by the cache generation /*{{{*/ +bool pkgCache::DepIterator::IsImplicit() const +{ + if (IsMultiArchImplicit() == true) + return true; + if (IsNegative() || S2->Type == pkgCache::Dep::Replaces) + { + if ((S2->CompareOp & pkgCache::Dep::ArchSpecific) != pkgCache::Dep::ArchSpecific && + strcmp(ParentPkg().Arch(), TargetPkg().Arch()) != 0) + return true; + } + return false; +} + /*}}}*/ +// ostream operator to handle string representation of a dependency /*{{{*/ +// --------------------------------------------------------------------- +/* */ +std::ostream& operator<<(std::ostream& out, pkgCache::DepIterator D) +{ + if (D.end() == true) + return out << "invalid dependency"; + + pkgCache::PkgIterator P = D.ParentPkg(); + pkgCache::PkgIterator T = D.TargetPkg(); + + out << (P.end() ? "invalid pkg" : P.FullName(false)) << " " << D.DepType() + << " on "; +APT_IGNORE_DEPRECATED_PUSH + if (T.end() == true) + out << "invalid pkg"; + else + out << T; +APT_IGNORE_DEPRECATED_POP + + if (D->Version != 0) + out << " (" << D.CompType() << " " << D.TargetVer() << ")"; + + return out; +} + /*}}}*/ +// VerIterator::CompareVer - Fast version compare for same pkgs /*{{{*/ +// --------------------------------------------------------------------- +/* This just looks over the version list to see if B is listed before A. In + most cases this will return in under 4 checks, ver lists are short. */ +int pkgCache::VerIterator::CompareVer(const VerIterator &B) const +{ + // Check if they are equal + if (*this == B) + return 0; + if (end() == true) + return -1; + if (B.end() == true) + return 1; + + /* Start at A and look for B. If B is found then A > B otherwise + B was before A so A < B */ + VerIterator I = *this; + for (;I.end() == false; ++I) + if (I == B) + return 1; + return -1; +} + /*}}}*/ +// VerIterator::Downloadable - Checks if the version is downloadable /*{{{*/ +// --------------------------------------------------------------------- +/* */ +APT_PURE bool pkgCache::VerIterator::Downloadable() const +{ + VerFileIterator Files = FileList(); + for (; Files.end() == false; ++Files) + if (Files.File().Flagged(pkgCache::Flag::NotSource) == false) + return true; + return false; +} + /*}}}*/ +// VerIterator::Automatic - Check if this version is 'automatic' /*{{{*/ +// --------------------------------------------------------------------- +/* This checks to see if any of the versions files are not NotAutomatic. + True if this version is selectable for automatic installation. */ +APT_PURE bool pkgCache::VerIterator::Automatic() const +{ + VerFileIterator Files = FileList(); + for (; Files.end() == false; ++Files) + // Do not check ButAutomaticUpgrades here as it is kind of automatic… + if (Files.File().Flagged(pkgCache::Flag::NotAutomatic) == false) + return true; + return false; +} + /*}}}*/ +// VerIterator::NewestFile - Return the newest file version relation /*{{{*/ +// --------------------------------------------------------------------- +/* This looks at the version numbers associated with all of the sources + this version is in and returns the highest.*/ +pkgCache::VerFileIterator pkgCache::VerIterator::NewestFile() const +{ + VerFileIterator Files = FileList(); + VerFileIterator Highest = Files; + for (; Files.end() == false; ++Files) + { + if (Owner->VS->CmpReleaseVer(Files.File().Version(),Highest.File().Version()) > 0) + Highest = Files; + } + + return Highest; +} + /*}}}*/ +// VerIterator::RelStr - Release description string /*{{{*/ +// --------------------------------------------------------------------- +/* This describes the version from a release-centric manner. The output is a + list of Label:Version/Archive */ +static std::string PkgFileIteratorToRelString(pkgCache::PkgFileIterator const &File) +{ + std::string Res; + if (File.Label() != 0) + Res = Res + File.Label() + ':'; + + if (File.Archive() != 0) + { + if (File.Version() == 0) + Res += File.Archive(); + else + Res = Res + File.Version() + '/' + File.Archive(); + } + else + { + // No release file, print the host name that this came from + if (File.Site() == 0 || File.Site()[0] == 0) + Res += "localhost"; + else + Res += File.Site(); + } + return Res; +} +string pkgCache::VerIterator::RelStr() const +{ + std::vector<std::string> RelStrs; + for (pkgCache::VerFileIterator I = this->FileList(); I.end() == false; ++I) + { + // Do not print 'not source' entries' + pkgCache::PkgFileIterator const File = I.File(); + if (File.Flagged(pkgCache::Flag::NotSource)) + continue; + + std::string const RS = PkgFileIteratorToRelString(File); + if (std::find(RelStrs.begin(), RelStrs.end(), RS) != RelStrs.end()) + continue; + + RelStrs.push_back(RS); + } + std::ostringstream os; + if (likely(RelStrs.empty() == false)) + { + std::copy(RelStrs.begin(), RelStrs.end()-1, std::ostream_iterator<std::string>(os, ", ")); + os << *RelStrs.rbegin(); + } + if (S->ParentPkg != 0) + os << " [" << Arch() << "]"; + return os.str(); +} + /*}}}*/ +// VerIterator::MultiArchType - string representing MultiArch flag /*{{{*/ +const char * pkgCache::VerIterator::MultiArchType() const +{ + if ((S->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same) + return "same"; + else if ((S->MultiArch & pkgCache::Version::Foreign) == pkgCache::Version::Foreign) + return "foreign"; + else if ((S->MultiArch & pkgCache::Version::Allowed) == pkgCache::Version::Allowed) + return "allowed"; + return "none"; +} + /*}}}*/ +// RlsFileIterator::IsOk - Checks if the cache is in sync with the file /*{{{*/ +// --------------------------------------------------------------------- +/* This stats the file and compares its stats with the ones that were + stored during generation. Date checks should probably also be + included here. */ +bool pkgCache::RlsFileIterator::IsOk() +{ + return true; +} + /*}}}*/ +// RlsFileIterator::RelStr - Return the release string /*{{{*/ +string pkgCache::RlsFileIterator::RelStr() +{ + string Res; + if (Version() != 0) + Res = Res + (Res.empty() == true?"v=":",v=") + Version(); + if (Origin() != 0) + Res = Res + (Res.empty() == true?"o=":",o=") + Origin(); + if (Archive() != 0) + Res = Res + (Res.empty() == true?"a=":",a=") + Archive(); + if (Codename() != 0) + Res = Res + (Res.empty() == true?"n=":",n=") + Codename(); + if (Label() != 0) + Res = Res + (Res.empty() == true?"l=":",l=") + Label(); + return Res; +} + /*}}}*/ +// PkgFileIterator::IsOk - Checks if the cache is in sync with the file /*{{{*/ +// --------------------------------------------------------------------- +/* This stats the file and compares its stats with the ones that were + stored during generation. Date checks should probably also be + included here. */ +bool pkgCache::PkgFileIterator::IsOk() +{ + return true; +} + /*}}}*/ +string pkgCache::PkgFileIterator::RelStr() /*{{{*/ +{ + std::string Res; + if (ReleaseFile() == 0) + { + if (Component() != 0) + Res = Res + (Res.empty() == true?"a=":",a=") + Component(); + } + else + { + Res = ReleaseFile().RelStr(); + if (Component() != 0) + Res = Res + (Res.empty() == true?"c=":",c=") + Component(); + } + if (Architecture() != 0) + Res = Res + (Res.empty() == true?"b=":",b=") + Architecture(); + return Res; +} + /*}}}*/ +// VerIterator::TranslatedDescription - Return the a DescIter for locale/*{{{*/ +// --------------------------------------------------------------------- +/* return a DescIter for the current locale or the default if none is + * found + */ +pkgCache::DescIterator pkgCache::VerIterator::TranslatedDescription() const +{ + std::vector<string> const lang = APT::Configuration::getLanguages(); + for (std::vector<string>::const_iterator l = lang.begin(); + l != lang.end(); ++l) + { + pkgCache::DescIterator Desc = DescriptionList(); + for (; Desc.end() == false; ++Desc) + if (*l == Desc.LanguageCode()) + break; + if (Desc.end() == true) + { + if (*l == "en") + { + Desc = DescriptionList(); + for (; Desc.end() == false; ++Desc) + if (strcmp(Desc.LanguageCode(), "") == 0) + break; + if (Desc.end() == true) + continue; + } + else + continue; + } + return Desc; + } + for (pkgCache::DescIterator Desc = DescriptionList(); + Desc.end() == false; ++Desc) + if (strcmp(Desc.LanguageCode(), "") == 0) + return Desc; + return DescriptionList(); +} + + /*}}}*/ + +pkgCache::~pkgCache() {} diff --git a/apt-pkg/pkgcache.h b/apt-pkg/pkgcache.h new file mode 100644 index 0000000..787e399 --- /dev/null +++ b/apt-pkg/pkgcache.h @@ -0,0 +1,831 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/**\file pkgcache.h + \brief pkgCache - Structure definitions for the cache file + + The goal of the cache file is two fold: + Firstly to speed loading and processing of the package file array and + secondly to reduce memory consumption of the package file array. + + The implementation is aimed at an environment with many primary package + files, for instance someone that has a Package file for their CD-ROM, a + Package file for the latest version of the distribution on the CD-ROM and a + package file for the development version. Always present is the information + contained in the status file which might be considered a separate package + file. + + Please understand, this is designed as a <b>Cache file</b> it is not meant to be + used on any system other than the one it was created for. It is not meant to + be authoritative either, i.e. if a system crash or software failure occurs it + must be perfectly acceptable for the cache file to be in an inconsistent + state. Furthermore at any time the cache file may be erased without losing + any information. + + Also the structures and storage layout is optimized for use by the APT + and may not be suitable for all purposes. However it should be possible + to extend it with associate cache files that contain other information. + + To keep memory use down the cache file only contains often used fields and + fields that are inexpensive to store, the Package file has a full list of + fields. Also the client may assume that all items are perfectly valid and + need not perform checks against their correctness. Removal of information + from the cache is possible, but blanks will be left in the file, and + unused strings will also be present. The recommended implementation is to + simply rebuild the cache each time any of the data files change. It is + possible to add a new package file to the cache without any negative side + effects. + + <b>Note on Pointer access</b> + Clients should always use the CacheIterators classes for access to the + cache and the data in it. They also provide a simple STL-like method for + traversing the links of the datastructure. + + Every item in every structure is stored as the index to that structure. + What this means is that once the files is mmaped every data access has to + go through a fix up stage to get a real memory pointer. This is done + by taking the index, multiplying it by the type size and then adding + it to the start address of the memory block. This sounds complex, but + in C it is a single array dereference. Because all items are aligned to + their size and indexes are stored as multiples of the size of the structure + the format is immediately portable to all possible architectures - BUT the + generated files are -NOT-. + + This scheme allows code like this to be written: + <example> + void *Map = mmap(...); + Package *PkgList = (Package *)Map; + Header *Head = (Header *)Map; + char *Strings = (char *)Map; + cout << (Strings + PkgList[Head->HashTable[0]]->Name) << endl; + </example> + Notice the lack of casting or multiplication. The net result is to return + the name of the first package in the first hash bucket, without error + checks. + + The generator uses allocation pools to group similarly sized structures in + large blocks to eliminate any alignment overhead. The generator also + assures that no structures overlap and all indexes are unique. Although + at first glance it may seem like there is the potential for two structures + to exist at the same point the generator never allows this to happen. + (See the discussion of free space pools) + + See \ref pkgcachegen.h for more information about generating cache structures. */ + /*}}}*/ +#ifndef PKGLIB_PKGCACHE_H +#define PKGLIB_PKGCACHE_H +#define __PKGLIB_IN_PKGCACHE_H +#include <apt-pkg/macros.h> +#include <apt-pkg/mmap.h> + +#include <string> +#include <stdint.h> +#include <time.h> + +#ifdef APT_PKG_EXPOSE_STRING_VIEW +#include <apt-pkg/string_view.h> +#endif + +#ifndef APT_8_CLEANER_HEADERS +using std::string; +#endif + +// size of (potentially big) files like debs or the install size of them +typedef uint64_t map_filesize_t; +// storing file sizes of indexes, which are way below 4 GB for now +typedef uint32_t map_filesize_small_t; +// each package/group/dependency gets an id +typedef uint32_t map_id_t; +// some files get an id, too, but in far less absolute numbers +typedef uint16_t map_fileid_t; +// relative pointer from cache start +typedef uint32_t map_pointer_t; +// same as the previous, but documented to be to a string item +typedef map_pointer_t map_stringitem_t; +// we have only a small amount of flags for each item +typedef uint8_t map_flags_t; +typedef uint8_t map_number_t; + +class pkgVersioningSystem; +class pkgCache /*{{{*/ +{ + public: + // Cache element predeclarations + struct Header; + struct Group; + struct Package; + struct ReleaseFile; + struct PackageFile; + struct Version; + struct Description; + struct Provides; + struct Dependency; + struct DependencyData; + struct StringItem; + struct VerFile; + struct DescFile; + + // Iterators + template<typename Str, typename Itr> class Iterator; + class GrpIterator; + class PkgIterator; + class VerIterator; + class DescIterator; + class DepIterator; + class PrvIterator; + class RlsFileIterator; + class PkgFileIterator; + class VerFileIterator; + class DescFileIterator; + + class Namespace; + + // These are all the constants used in the cache structures + + // WARNING - if you change these lists you must also edit + // the stringification in pkgcache.cc and also consider whether + // the cache file will become incompatible. + struct Dep + { + enum DepType {Depends=1,PreDepends=2,Suggests=3,Recommends=4, + Conflicts=5,Replaces=6,Obsoletes=7,DpkgBreaks=8,Enhances=9}; + /** \brief available compare operators + + The lower 4 bits are used to indicate what operator is being specified and + the upper 4 bits are flags. OR indicates that the next package is + or'd with the current package. */ + enum DepCompareOp {NoOp=0,LessEq=0x1,GreaterEq=0x2,Less=0x3, + Greater=0x4,Equals=0x5,NotEquals=0x6, + Or=0x10, /*!< or'ed with the next dependency */ + MultiArchImplicit=0x20, /*!< generated internally, not spelled out in the index */ + ArchSpecific=0x40 /*!< was decorated with an explicit architecture in index */ + }; + }; + + struct State + { + /** \brief priority of a package version + + Zero is used for unparsable or absent Priority fields. */ + enum VerPriority {Required=1,Important=2,Standard=3,Optional=4,Extra=5}; + enum PkgSelectedState {Unknown=0,Install=1,Hold=2,DeInstall=3,Purge=4}; + enum PkgInstState {Ok=0,ReInstReq=1,HoldInst=2,HoldReInstReq=3}; + enum PkgCurrentState {NotInstalled=0,UnPacked=1,HalfConfigured=2, + HalfInstalled=4,ConfigFiles=5,Installed=6, + TriggersAwaited=7,TriggersPending=8}; + }; + + struct Flag + { + enum PkgFlags {Auto=(1<<0),Essential=(1<<3),Important=(1<<4)}; + enum PkgFFlags { + NotSource=(1<<0), /*!< packages can't be fetched from here, e.g. dpkg/status file */ + LocalSource=(1<<1), /*!< local sources can't and will not be verified by hashes */ + NoPackages=(1<<2), /*!< the file includes no package records itself, but additions like Translations */ + }; + enum ReleaseFileFlags + { + NotAutomatic = (1 << 0), /*!< archive has a default pin of 1 */ + ButAutomaticUpgrades = (1 << 1), /*!< (together with the previous) archive has a default pin of 100 */ + PackagesRequireAuthorization = (1 << 2), /*!< (together with the previous) archive has a default pin of 100 */ + }; + enum ProvidesFlags { + MultiArchImplicit=pkgCache::Dep::MultiArchImplicit, /*!< generated internally, not spelled out in the index */ + ArchSpecific=pkgCache::Dep::ArchSpecific /*!< was decorated with an explicit architecture in index */ + }; + }; + + protected: + + // Memory mapped cache file + std::string CacheFile; + MMap ⤅ +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN map_id_t sHash(APT::StringView S) const APT_PURE; +#endif + map_id_t sHash(const std::string &S) const APT_PURE; + map_id_t sHash(const char *S) const APT_PURE; + + public: + + // Pointers to the arrays of items + Header *HeaderP; + Group *GrpP; + Package *PkgP; + VerFile *VerFileP; + DescFile *DescFileP; + ReleaseFile *RlsFileP; + PackageFile *PkgFileP; + Version *VerP; + Description *DescP; + Provides *ProvideP; + Dependency *DepP; + DependencyData *DepDataP; + APT_DEPRECATED_MSG("Not used anymore in cache generation and without a replacement") StringItem *StringItemP; + char *StrP; + + virtual bool ReMap(bool const &Errorchecks = true); + inline bool Sync() {return Map.Sync();} + inline MMap &GetMap() {return Map;} + inline void *DataEnd() {return ((unsigned char *)Map.Data()) + Map.Size();} + + // String hashing function (512 range) +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN inline map_id_t Hash(APT::StringView S) const {return sHash(S);} +#endif + inline map_id_t Hash(const std::string &S) const {return sHash(S);} + inline map_id_t Hash(const char *S) const {return sHash(S);} + + APT_HIDDEN uint32_t CacheHash(); + + // Useful transformation things + static const char *Priority(unsigned char Priority); + + // Accessors +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN GrpIterator FindGrp(APT::StringView Name); + APT_HIDDEN PkgIterator FindPkg(APT::StringView Name); + APT_HIDDEN PkgIterator FindPkg(APT::StringView Name, APT::StringView Arch); +#endif + +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT::StringView ViewString(map_stringitem_t idx) const + { + char *name = StrP + idx; + uint16_t len = *reinterpret_cast<const uint16_t*>(name - sizeof(uint16_t)); + return APT::StringView(name, len); + } +#endif + + + GrpIterator FindGrp(const std::string &Name); + PkgIterator FindPkg(const std::string &Name); + PkgIterator FindPkg(const std::string &Name, const std::string &Arch); + + Header &Head() {return *HeaderP;} + inline GrpIterator GrpBegin(); + inline GrpIterator GrpEnd(); + inline PkgIterator PkgBegin(); + inline PkgIterator PkgEnd(); + inline PkgFileIterator FileBegin(); + inline PkgFileIterator FileEnd(); + inline RlsFileIterator RlsFileBegin(); + inline RlsFileIterator RlsFileEnd(); + + inline bool MultiArchCache() const { return MultiArchEnabled; } + inline char const * NativeArch(); + + // Make me a function + pkgVersioningSystem *VS; + + // Converters + static const char *CompTypeDeb(unsigned char Comp) APT_PURE; + static const char *CompType(unsigned char Comp) APT_PURE; + static const char *DepType(unsigned char Dep); + + pkgCache(MMap *Map,bool DoMap = true); + virtual ~pkgCache(); + +private: + void * const d; + bool MultiArchEnabled; +}; + /*}}}*/ +// Header structure /*{{{*/ +struct pkgCache::Header +{ + /** \brief Signature information + + This must contain the hex value 0x98FE76DC which is designed to + verify that the system loading the image has the same byte order + and byte size as the system saving the image */ + uint32_t Signature; + /** These contain the version of the cache file */ + map_number_t MajorVersion; + map_number_t MinorVersion; + /** \brief indicates if the cache should be erased + + Dirty is true if the cache file was opened for reading, the client + expects to have written things to it and have not fully synced it. + The file should be erased and rebuilt if it is true. */ + bool Dirty; + + /** \brief Size of structure values + + All *Sz variables contains the sizeof() that particular structure. + It is used as an extra consistency check on the structure of the file. + + If any of the size values do not exactly match what the client expects + then the client should refuse the load the file. */ + uint16_t HeaderSz; + map_number_t GroupSz; + map_number_t PackageSz; + map_number_t ReleaseFileSz; + map_number_t PackageFileSz; + map_number_t VersionSz; + map_number_t DescriptionSz; + map_number_t DependencySz; + map_number_t DependencyDataSz; + map_number_t ProvidesSz; + map_number_t VerFileSz; + map_number_t DescFileSz; + + /** \brief Structure counts + + These indicate the number of each structure contained in the cache. + PackageCount is especially useful for generating user state structures. + See Package::Id for more info. */ + map_id_t GroupCount; + map_id_t PackageCount; + map_id_t VersionCount; + map_id_t DescriptionCount; + map_id_t DependsCount; + map_id_t DependsDataCount; + map_fileid_t ReleaseFileCount; + map_fileid_t PackageFileCount; + map_fileid_t VerFileCount; + map_fileid_t DescFileCount; + map_id_t ProvidesCount; + + /** \brief index of the first PackageFile structure + + The PackageFile structures are singly linked lists that represent + all package files that have been merged into the cache. */ + map_pointer_t FileList; + /** \brief index of the first ReleaseFile structure */ + map_pointer_t RlsFileList; + + /** \brief String representing the version system used */ + map_pointer_t VerSysName; + /** \brief native architecture the cache was built against */ + map_pointer_t Architecture; + /** \brief all architectures the cache was built against */ + map_pointer_t Architectures; + /** \brief The maximum size of a raw entry from the original Package file */ + map_filesize_t MaxVerFileSize; + /** \brief The maximum size of a raw entry from the original Translation file */ + map_filesize_t MaxDescFileSize; + + /** \brief The Pool structures manage the allocation pools that the generator uses + + Start indicates the first byte of the pool, Count is the number of objects + remaining in the pool and ItemSize is the structure size (alignment factor) + of the pool. An ItemSize of 0 indicates the pool is empty. There should be + the same number of pools as there are structure types. The generator + stores this information so future additions can make use of any unused pool + blocks. */ + DynamicMMap::Pool Pools[12]; + + /** \brief hash tables providing rapid group/package name lookup + + Each group/package name is inserted into a hash table using pkgCache::Hash(const &string) + By iterating over each entry in the hash table it is possible to iterate over + the entire list of packages. Hash Collisions are handled with a singly linked + list of packages based at the hash item. The linked list contains only + packages that match the hashing function. + In the PkgHashTable is it possible that multiple packages have the same name - + these packages are stored as a sequence in the list. + The size of both tables is the same. */ + uint32_t HashTableSize; + uint32_t GetHashTableSize() const { return HashTableSize; } + void SetHashTableSize(unsigned int const sz) { HashTableSize = sz; } + map_pointer_t GetArchitectures() const { return Architectures; } + void SetArchitectures(map_pointer_t const idx) { Architectures = idx; } + map_pointer_t * PkgHashTableP() const { return (map_pointer_t*) (this + 1); } + map_pointer_t * GrpHashTableP() const { return PkgHashTableP() + GetHashTableSize(); } + + /** \brief Hash of the file (TODO: Rename) */ + map_filesize_small_t CacheFileSize; + + bool CheckSizes(Header &Against) const APT_PURE; + Header(); +}; + /*}}}*/ +// Group structure /*{{{*/ +/** \brief groups architecture depending packages together + + On or more packages with the same name form a group, so we have + a simple way to access a package built for different architectures + Group exists in a singly linked list of group records starting at + the hash index of the name in the pkgCache::Header::GrpHashTable */ +struct pkgCache::Group +{ + /** \brief Name of the group */ + map_stringitem_t Name; + + // Linked List + /** \brief Link to the first package which belongs to the group */ + map_pointer_t FirstPackage; // Package + /** \brief Link to the last package which belongs to the group */ + map_pointer_t LastPackage; // Package + /** \brief Link to the next Group */ + map_pointer_t Next; // Group + /** \brief unique sequel ID */ + map_id_t ID; + +}; + /*}}}*/ +// Package structure /*{{{*/ +/** \brief contains information for a single unique package + + There can be any number of versions of a given package. + Package exists in a singly linked list of package records starting at + the hash index of the name in the pkgCache::Header::PkgHashTable + + A package can be created for every architecture so package names are + not unique, but it is guaranteed that packages with the same name + are sequencel ordered in the list. Packages with the same name can be + accessed with the Group. +*/ +struct pkgCache::Package +{ + /** \brief Name of the package + * Note that the access method Name() will remain. It is just this data member + * deprecated as this information is already stored and available via the + * associated Group – so it is wasting precious binary cache space */ + APT_DEPRECATED_MSG("Use the .Name() method instead of accessing the member directly") map_stringitem_t Name; + /** \brief Architecture of the package */ + map_stringitem_t Arch; + /** \brief Base of a singly linked list of versions + + Each structure represents a unique version of the package. + The version structures contain links into PackageFile and the + original text file as well as detailed information about the size + and dependencies of the specific package. In this way multiple + versions of a package can be cleanly handled by the system. + Furthermore, this linked list is guaranteed to be sorted + from Highest version to lowest version with no duplicate entries. */ + map_pointer_t VersionList; // Version + /** \brief index to the installed version */ + map_pointer_t CurrentVer; // Version + /** \brief index of the group this package belongs to */ + map_pointer_t Group; // Group the Package belongs to + + // Linked list + /** \brief Link to the next package in the same bucket */ + map_pointer_t NextPackage; // Package + /** \brief List of all dependencies on this package */ + map_pointer_t RevDepends; // Dependency + /** \brief List of all "packages" this package provide */ + map_pointer_t ProvidesList; // Provides + + // Install/Remove/Purge etc + /** \brief state that the user wishes the package to be in */ + map_number_t SelectedState; // What + /** \brief installation state of the package + + This should be "ok" but in case the installation failed + it will be different. + */ + map_number_t InstState; // Flags + /** \brief indicates if the package is installed */ + map_number_t CurrentState; // State + + /** \brief unique sequel ID + + ID is a unique value from 0 to Header->PackageCount assigned by the generator. + This allows clients to create an array of size PackageCount and use it to store + state information for the package map. For instance the status file emitter uses + this to track which packages have been emitted already. */ + map_id_t ID; + /** \brief some useful indicators of the package's state */ + map_flags_t Flags; +}; + /*}}}*/ +// Release File structure /*{{{*/ +/** \brief stores information about the release files used to generate the cache + + PackageFiles reference ReleaseFiles as we need to keep record of which + version belongs to which release e.g. for pinning. */ +struct pkgCache::ReleaseFile +{ + /** \brief physical disk file that this ReleaseFile represents */ + map_stringitem_t FileName; + /** \brief the release information + + Please see the files document for a description of what the + release information means. */ + map_stringitem_t Archive; + map_stringitem_t Codename; + map_stringitem_t Version; + map_stringitem_t Origin; + map_stringitem_t Label; + /** \brief The site the index file was fetched from */ + map_stringitem_t Site; + + /** \brief Size of the file + + Used together with the modification time as a + simple check to ensure that the Packages + file has not been altered since Cache generation. */ + map_filesize_t Size; + /** \brief Modification time for the file */ + time_t mtime; + + /** @TODO document PackageFile::Flags */ + map_flags_t Flags; + + // Linked list + /** \brief Link to the next ReleaseFile in the Cache */ + map_pointer_t NextFile; + /** \brief unique sequel ID */ + map_fileid_t ID; +}; + /*}}}*/ +// Package File structure /*{{{*/ +/** \brief stores information about the files used to generate the cache + + Package files are referenced by Version structures to be able to know + after the generation still from which Packages file includes this Version + as we need this information later on e.g. for pinning. */ +struct pkgCache::PackageFile +{ + /** \brief physical disk file that this PackageFile represents */ + map_stringitem_t FileName; + /** \brief the release information */ + map_pointer_t Release; + + map_stringitem_t Component; + map_stringitem_t Architecture; + + /** \brief indicates what sort of index file this is + + @TODO enumerate at least the possible indexes */ + map_stringitem_t IndexType; + /** \brief Size of the file + + Used together with the modification time as a + simple check to ensure that the Packages + file has not been altered since Cache generation. */ + map_filesize_t Size; + /** \brief Modification time for the file */ + time_t mtime; + + /** @TODO document PackageFile::Flags */ + map_flags_t Flags; + + // Linked list + /** \brief Link to the next PackageFile in the Cache */ + map_pointer_t NextFile; // PackageFile + /** \brief unique sequel ID */ + map_fileid_t ID; +}; + /*}}}*/ +// VerFile structure /*{{{*/ +/** \brief associates a version with a PackageFile + + This allows a full description of all Versions in all files + (and hence all sources) under consideration. */ +struct pkgCache::VerFile +{ + /** \brief index of the package file that this version was found in */ + map_pointer_t File; // PackageFile + /** \brief next step in the linked list */ + map_pointer_t NextFile; // PkgVerFile + /** \brief position in the package file */ + map_filesize_t Offset; // File offset + /** @TODO document pkgCache::VerFile::Size */ + map_filesize_t Size; +}; + /*}}}*/ +// DescFile structure /*{{{*/ +/** \brief associates a description with a Translation file */ +struct pkgCache::DescFile +{ + /** \brief index of the file that this description was found in */ + map_pointer_t File; // PackageFile + /** \brief next step in the linked list */ + map_pointer_t NextFile; // PkgVerFile + /** \brief position in the file */ + map_filesize_t Offset; // File offset + /** @TODO document pkgCache::DescFile::Size */ + map_filesize_t Size; +}; + /*}}}*/ +// Version structure /*{{{*/ +/** \brief information for a single version of a package + + The version list is always sorted from highest version to lowest + version by the generator. Equal version numbers are either merged + or handled as separate versions based on the Hash value. */ +APT_IGNORE_DEPRECATED_PUSH +struct pkgCache::Version +{ + /** \brief complete version string */ + map_stringitem_t VerStr; + /** \brief section this version is filled in */ + map_stringitem_t Section; + /** \brief source package name this version comes from + Always contains the name, even if it is the same as the binary name */ + map_stringitem_t SourcePkgName; + /** \brief source version this version comes from + Always contains the version string, even if it is the same as the binary version */ + map_stringitem_t SourceVerStr; + + /** \brief Multi-Arch capabilities of a package version */ + enum VerMultiArch { No = 0, /*!< is the default and doesn't trigger special behaviour */ + All = (1<<0), /*!< will cause that Ver.Arch() will report "all" */ + Foreign = (1<<1), /*!< can satisfy dependencies in another architecture */ + Same = (1<<2), /*!< can be co-installed with itself from other architectures */ + Allowed = (1<<3), /*!< other packages are allowed to depend on thispkg:any */ + AllForeign = All | Foreign, + AllAllowed = All | Allowed }; + + /** \brief deprecated variant of No */ + static const APT_DEPRECATED_MSG("The default value of the Multi-Arch field is no, not none") VerMultiArch None = No; + + /** \brief stores the MultiArch capabilities of this version + + Flags used are defined in pkgCache::Version::VerMultiArch + */ + map_number_t MultiArch; + + /** \brief references all the PackageFile's that this version came from + + FileList can be used to determine what distribution(s) the Version + applies to. If FileList is 0 then this is a blank version. + The structure should also have a 0 in all other fields excluding + pkgCache::Version::VerStr and Possibly pkgCache::Version::NextVer. */ + map_pointer_t FileList; // VerFile + /** \brief next (lower or equal) version in the linked list */ + map_pointer_t NextVer; // Version + /** \brief next description in the linked list */ + map_pointer_t DescriptionList; // Description + /** \brief base of the dependency list */ + map_pointer_t DependsList; // Dependency + /** \brief links to the owning package + + This allows reverse dependencies to determine the package */ + map_pointer_t ParentPkg; // Package + /** \brief list of pkgCache::Provides */ + map_pointer_t ProvidesList; // Provides + + /** \brief archive size for this version + + For Debian this is the size of the .deb file. */ + map_filesize_t Size; // These are the .deb size + /** \brief uncompressed size for this version */ + map_filesize_t InstalledSize; + /** \brief characteristic value representing this version + + No two packages in existence should have the same VerStr + and Hash with different contents. */ + unsigned short Hash; + /** \brief unique sequel ID */ + map_id_t ID; + /** \brief parsed priority value */ + map_number_t Priority; +}; +APT_IGNORE_DEPRECATED_POP + /*}}}*/ +// Description structure /*{{{*/ +/** \brief datamember of a linked list of available description for a version */ +struct pkgCache::Description +{ + /** \brief Language code of this description (translation) + + If the value has a 0 length then this is read using the Package + file else the Translation-CODE file is used. */ + map_stringitem_t language_code; + /** \brief MD5sum of the original description + + Used to map Translations of a description to a version + and to check that the Translation is up-to-date. */ + map_stringitem_t md5sum; + + /** @TODO document pkgCache::Description::FileList */ + map_pointer_t FileList; // DescFile + /** \brief next translation for this description */ + map_pointer_t NextDesc; // Description + /** \brief the text is a description of this package */ + map_pointer_t ParentPkg; // Package + + /** \brief unique sequel ID */ + map_id_t ID; +}; + /*}}}*/ +// Dependency structure /*{{{*/ +/** \brief information for a single dependency record + + The records are split up like this to ease processing by the client. + The base of the linked list is pkgCache::Version::DependsList. + All forms of dependencies are recorded here including Depends, + Recommends, Suggests, Enhances, Conflicts, Replaces and Breaks. */ +struct pkgCache::DependencyData +{ + /** \brief string of the version the dependency is applied against */ + map_stringitem_t Version; + /** \brief index of the package this depends applies to + + The generator will - if the package does not already exist - + create a blank (no version records) package. */ + map_pointer_t Package; // Package + + /** \brief Dependency type - Depends, Recommends, Conflicts, etc */ + map_number_t Type; + /** \brief comparison operator specified on the depends line + + If the high bit is set then it is a logical OR with the previous record. */ + map_flags_t CompareOp; + + map_pointer_t NextData; +}; +struct pkgCache::Dependency +{ + map_pointer_t DependencyData; // DependencyData + /** \brief version of the package which has the depends */ + map_pointer_t ParentVer; // Version + /** \brief next reverse dependency of this package */ + map_pointer_t NextRevDepends; // Dependency + /** \brief next dependency of this version */ + map_pointer_t NextDepends; // Dependency + + /** \brief unique sequel ID */ + map_id_t ID; +}; + /*}}}*/ +// Provides structure /*{{{*/ +/** \brief handles virtual packages + + When a Provides: line is encountered a new provides record is added + associating the package with a virtual package name. + The provides structures are linked off the package structures. + This simplifies the analysis of dependencies and other aspects A provides + refers to a specific version of a specific package, not all versions need to + provide that provides.*/ +struct pkgCache::Provides +{ + /** \brief index of the package providing this */ + map_pointer_t ParentPkg; // Package + /** \brief index of the version this provide line applies to */ + map_pointer_t Version; // Version + /** \brief version in the provides line (if any) + + This version allows dependencies to depend on specific versions of a + Provides, as well as allowing Provides to override existing packages. */ + map_stringitem_t ProvideVersion; + map_flags_t Flags; + /** \brief next provides (based of package) */ + map_pointer_t NextProvides; // Provides + /** \brief next provides (based of version) */ + map_pointer_t NextPkgProv; // Provides +}; + /*}}}*/ +// UNUSED StringItem structure /*{{{*/ +struct APT_DEPRECATED_MSG("No longer used in cache generation without a replacement") pkgCache::StringItem +{ + /** \brief string this refers to */ + map_ptrloc String; // StringItem + /** \brief Next link in the chain */ + map_ptrloc NextItem; // StringItem +}; + /*}}}*/ + +inline char const * pkgCache::NativeArch() + { return StrP + HeaderP->Architecture; } + +#include <apt-pkg/cacheiterators.h> + + inline pkgCache::GrpIterator pkgCache::GrpBegin() + { + return GrpIterator(*this); + } + inline pkgCache::GrpIterator pkgCache::GrpEnd() + { + return GrpIterator(*this, GrpP);} +inline pkgCache::PkgIterator pkgCache::PkgBegin() + {return PkgIterator(*this);} +inline pkgCache::PkgIterator pkgCache::PkgEnd() + {return PkgIterator(*this,PkgP);} +inline pkgCache::PkgFileIterator pkgCache::FileBegin() + {return PkgFileIterator(*this,PkgFileP + HeaderP->FileList);} +inline pkgCache::PkgFileIterator pkgCache::FileEnd() + {return PkgFileIterator(*this,PkgFileP);} +inline pkgCache::RlsFileIterator pkgCache::RlsFileBegin() + {return RlsFileIterator(*this,RlsFileP + HeaderP->RlsFileList);} +inline pkgCache::RlsFileIterator pkgCache::RlsFileEnd() + {return RlsFileIterator(*this,RlsFileP);} + + +// Oh I wish for Real Name Space Support +class pkgCache::Namespace /*{{{*/ +{ + public: + typedef pkgCache::GrpIterator GrpIterator; + typedef pkgCache::PkgIterator PkgIterator; + typedef pkgCache::VerIterator VerIterator; + typedef pkgCache::DescIterator DescIterator; + typedef pkgCache::DepIterator DepIterator; + typedef pkgCache::PrvIterator PrvIterator; + typedef pkgCache::RlsFileIterator RlsFileIterator; + typedef pkgCache::PkgFileIterator PkgFileIterator; + typedef pkgCache::VerFileIterator VerFileIterator; + typedef pkgCache::Version Version; + typedef pkgCache::Description Description; + typedef pkgCache::Package Package; + typedef pkgCache::Header Header; + typedef pkgCache::Dep Dep; + typedef pkgCache::Flag Flag; +}; + /*}}}*/ +#undef __PKGLIB_IN_PKGCACHE_H +#endif diff --git a/apt-pkg/pkgcachegen.cc b/apt-pkg/pkgcachegen.cc new file mode 100644 index 0000000..5a3b65b --- /dev/null +++ b/apt-pkg/pkgcachegen.cc @@ -0,0 +1,1860 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Package Cache Generator - Generator for the cache structure. + + This builds the cache structure from the abstract package list parser. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/hashsum_template.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/md5.h> +#include <apt-pkg/metaindex.h> +#include <apt-pkg/mmap.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgcachegen.h> +#include <apt-pkg/pkgsystem.h> +#include <apt-pkg/progress.h> +#include <apt-pkg/sourcelist.h> +#include <apt-pkg/version.h> + +#include <algorithm> +#include <iostream> +#include <memory> +#include <string> +#include <vector> +#include <stddef.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ +template<class T> using Dynamic = pkgCacheGenerator::Dynamic<T>; +typedef std::vector<pkgIndexFile *>::iterator FileIterator; +template <typename Iter> std::vector<Iter*> pkgCacheGenerator::Dynamic<Iter>::toReMap; + +static bool IsDuplicateDescription(pkgCache &Cache, pkgCache::DescIterator Desc, + APT::StringView CurMd5, std::string const &CurLang); + +using std::string; +using APT::StringView; + +// CacheGenerator::pkgCacheGenerator - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* We set the dirty flag and make sure that is written to the disk */ +pkgCacheGenerator::pkgCacheGenerator(DynamicMMap *pMap,OpProgress *Prog) : + Map(*pMap), Cache(pMap,false), Progress(Prog), + CurrentRlsFile(nullptr), CurrentFile(nullptr), d(nullptr) +{ +} +bool pkgCacheGenerator::Start() +{ + if (Map.Size() == 0) + { + // Setup the map interface.. + Cache.HeaderP = (pkgCache::Header *)Map.Data(); + _error->PushToStack(); + Map.RawAllocate(sizeof(pkgCache::Header)); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + if (newError) + return false; + if (Map.Size() <= 0) + return false; + + Map.UsePools(*Cache.HeaderP->Pools,sizeof(Cache.HeaderP->Pools)/sizeof(Cache.HeaderP->Pools[0])); + + // Starting header + *Cache.HeaderP = pkgCache::Header(); + + // make room for the hashtables for packages and groups + if (Map.RawAllocate(2 * (Cache.HeaderP->GetHashTableSize() * sizeof(map_pointer_t))) == 0) + return false; + + map_stringitem_t const idxVerSysName = WriteStringInMap(_system->VS->Label); + if (unlikely(idxVerSysName == 0)) + return false; + map_stringitem_t const idxArchitecture = StoreString(MIXED, _config->Find("APT::Architecture")); + if (unlikely(idxArchitecture == 0)) + return false; + map_stringitem_t idxArchitectures; + + std::vector<std::string> archs = APT::Configuration::getArchitectures(); + if (archs.size() > 1) + { + std::vector<std::string>::const_iterator a = archs.begin(); + std::string list = *a; + for (++a; a != archs.end(); ++a) + list.append(",").append(*a); + idxArchitectures = WriteStringInMap(list); + if (unlikely(idxArchitectures == 0)) + return false; + } + else + idxArchitectures = idxArchitecture; + + Cache.HeaderP = (pkgCache::Header *)Map.Data(); + Cache.HeaderP->VerSysName = idxVerSysName; + Cache.HeaderP->Architecture = idxArchitecture; + Cache.HeaderP->SetArchitectures(idxArchitectures); + + // Calculate the hash for the empty map, so ReMap does not fail + Cache.HeaderP->CacheFileSize = Cache.CacheHash(); + Cache.ReMap(); + } + else + { + // Map directly from the existing file + Cache.ReMap(); + Map.UsePools(*Cache.HeaderP->Pools,sizeof(Cache.HeaderP->Pools)/sizeof(Cache.HeaderP->Pools[0])); + if (Cache.VS != _system->VS) + return _error->Error(_("Cache has an incompatible versioning system")); + } + + Cache.HeaderP->Dirty = true; + Map.Sync(0,sizeof(pkgCache::Header)); + return true; +} + /*}}}*/ +// CacheGenerator::~pkgCacheGenerator - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* We sync the data then unset the dirty flag in two steps so as to + advoid a problem during a crash */ +pkgCacheGenerator::~pkgCacheGenerator() +{ + if (_error->PendingError() == true || Map.validData() == false) + return; + if (Map.Sync() == false) + return; + + Cache.HeaderP->Dirty = false; + Cache.HeaderP->CacheFileSize = Cache.CacheHash(); + + if (_config->FindB("Debug::pkgCacheGen", false)) + std::clog << "Produced cache with hash " << Cache.HeaderP->CacheFileSize << std::endl; + Map.Sync(0,sizeof(pkgCache::Header)); +} + /*}}}*/ +void pkgCacheGenerator::ReMap(void const * const oldMap, void const * const newMap, size_t oldSize) {/*{{{*/ + // Prevent multiple remaps of the same iterator. If seen.insert(iterator) + // returns (something, true) the iterator was not yet seen and we can + // remap it. + std::unordered_set<void *> seen; + if (oldMap == newMap) + return; + + if (_config->FindB("Debug::pkgCacheGen", false)) + std::clog << "Remapping from " << oldMap << " to " << newMap << std::endl; + + Cache.ReMap(false); + + if (CurrentFile != nullptr) + CurrentFile += static_cast<pkgCache::PackageFile const *>(newMap) - static_cast<pkgCache::PackageFile const *>(oldMap); + if (CurrentRlsFile != nullptr) + CurrentRlsFile += static_cast<pkgCache::ReleaseFile const *>(newMap) - static_cast<pkgCache::ReleaseFile const *>(oldMap); + + for (std::vector<pkgCache::GrpIterator*>::const_iterator i = Dynamic<pkgCache::GrpIterator>::toReMap.begin(); + i != Dynamic<pkgCache::GrpIterator>::toReMap.end(); ++i) + if (std::get<1>(seen.insert(*i)) == true) + (*i)->ReMap(oldMap, newMap); + for (std::vector<pkgCache::PkgIterator*>::const_iterator i = Dynamic<pkgCache::PkgIterator>::toReMap.begin(); + i != Dynamic<pkgCache::PkgIterator>::toReMap.end(); ++i) + if (std::get<1>(seen.insert(*i)) == true) + (*i)->ReMap(oldMap, newMap); + for (std::vector<pkgCache::VerIterator*>::const_iterator i = Dynamic<pkgCache::VerIterator>::toReMap.begin(); + i != Dynamic<pkgCache::VerIterator>::toReMap.end(); ++i) + if (std::get<1>(seen.insert(*i)) == true) + (*i)->ReMap(oldMap, newMap); + for (std::vector<pkgCache::DepIterator*>::const_iterator i = Dynamic<pkgCache::DepIterator>::toReMap.begin(); + i != Dynamic<pkgCache::DepIterator>::toReMap.end(); ++i) + if (std::get<1>(seen.insert(*i)) == true) + (*i)->ReMap(oldMap, newMap); + for (std::vector<pkgCache::DescIterator*>::const_iterator i = Dynamic<pkgCache::DescIterator>::toReMap.begin(); + i != Dynamic<pkgCache::DescIterator>::toReMap.end(); ++i) + if (std::get<1>(seen.insert(*i)) == true) + (*i)->ReMap(oldMap, newMap); + for (std::vector<pkgCache::PrvIterator*>::const_iterator i = Dynamic<pkgCache::PrvIterator>::toReMap.begin(); + i != Dynamic<pkgCache::PrvIterator>::toReMap.end(); ++i) + if (std::get<1>(seen.insert(*i)) == true) + (*i)->ReMap(oldMap, newMap); + for (std::vector<pkgCache::PkgFileIterator*>::const_iterator i = Dynamic<pkgCache::PkgFileIterator>::toReMap.begin(); + i != Dynamic<pkgCache::PkgFileIterator>::toReMap.end(); ++i) + if (std::get<1>(seen.insert(*i)) == true) + (*i)->ReMap(oldMap, newMap); + for (std::vector<pkgCache::RlsFileIterator*>::const_iterator i = Dynamic<pkgCache::RlsFileIterator>::toReMap.begin(); + i != Dynamic<pkgCache::RlsFileIterator>::toReMap.end(); ++i) + if (std::get<1>(seen.insert(*i)) == true) + (*i)->ReMap(oldMap, newMap); + for (APT::StringView* ViewP : Dynamic<APT::StringView>::toReMap) { + if (std::get<1>(seen.insert(ViewP)) == false) + continue; + // Ignore views outside of the cache. + if (ViewP->data() < static_cast<const char*>(oldMap) + || ViewP->data() > static_cast<const char*>(oldMap) + oldSize) + continue; + const char *data = ViewP->data() + (static_cast<const char*>(newMap) - static_cast<const char*>(oldMap)); + *ViewP = StringView(data , ViewP->size()); + } +} /*}}}*/ +// CacheGenerator::WriteStringInMap /*{{{*/ +map_stringitem_t pkgCacheGenerator::WriteStringInMap(const char *String, + const unsigned long &Len) { + size_t oldSize = Map.Size(); + void const * const oldMap = Map.Data(); + map_stringitem_t const index = Map.WriteString(String, Len); + if (index != 0) + ReMap(oldMap, Map.Data(), oldSize); + return index; +} + /*}}}*/ +// CacheGenerator::WriteStringInMap /*{{{*/ +map_stringitem_t pkgCacheGenerator::WriteStringInMap(const char *String) { + size_t oldSize = Map.Size(); + void const * const oldMap = Map.Data(); + map_stringitem_t const index = Map.WriteString(String); + if (index != 0) + ReMap(oldMap, Map.Data(), oldSize); + return index; +} + /*}}}*/ +map_pointer_t pkgCacheGenerator::AllocateInMap(const unsigned long &size) {/*{{{*/ + size_t oldSize = Map.Size(); + void const * const oldMap = Map.Data(); + map_pointer_t const index = Map.Allocate(size); + if (index != 0) + ReMap(oldMap, Map.Data(), oldSize); + return index; +} + /*}}}*/ +// CacheGenerator::MergeList - Merge the package list /*{{{*/ +// --------------------------------------------------------------------- +/* This provides the generation of the entries in the cache. Each loop + goes through a single package record from the underlying parse engine. */ +bool pkgCacheGenerator::MergeList(ListParser &List, + pkgCache::VerIterator *OutVer) +{ + List.Owner = this; + + unsigned int Counter = 0; + while (List.Step() == true) + { + string const PackageName = List.Package(); + if (PackageName.empty() == true) + return false; + + Counter++; + if (Counter % 100 == 0 && Progress != 0) + Progress->Progress(List.Offset()); + + APT::StringView Arch = List.Architecture(); + Dynamic<APT::StringView> DynArch(Arch); + APT::StringView Version = List.Version(); + Dynamic<APT::StringView> DynVersion(Version); + if (Version.empty() == true && Arch.empty() == true) + { + // package descriptions + if (MergeListGroup(List, PackageName) == false) + return false; + continue; + } + + // Get a pointer to the package structure + pkgCache::PkgIterator Pkg; + Dynamic<pkgCache::PkgIterator> DynPkg(Pkg); + if (NewPackage(Pkg, PackageName, Arch) == false) + // TRANSLATOR: The first placeholder is a package name, + // the other two should be copied verbatim as they include debug info + return _error->Error(_("Error occurred while processing %s (%s%d)"), + PackageName.c_str(), "NewPackage", 1); + + + if (Version.empty() == true) + { + if (MergeListPackage(List, Pkg) == false) + return false; + } + else + { + if (MergeListVersion(List, Pkg, Version, OutVer) == false) + return false; + } + + if (OutVer != 0) + return true; + } + + if (Cache.HeaderP->PackageCount >= std::numeric_limits<map_id_t>::max()) + return _error->Error(_("Wow, you exceeded the number of package " + "names this APT is capable of.")); + if (Cache.HeaderP->VersionCount >= std::numeric_limits<map_id_t>::max()) + return _error->Error(_("Wow, you exceeded the number of versions " + "this APT is capable of.")); + if (Cache.HeaderP->DescriptionCount >= std::numeric_limits<map_id_t>::max()) + return _error->Error(_("Wow, you exceeded the number of descriptions " + "this APT is capable of.")); + if (Cache.HeaderP->DependsCount >= std::numeric_limits<map_id_t>::max()) + return _error->Error(_("Wow, you exceeded the number of dependencies " + "this APT is capable of.")); + + return true; +} +// CacheGenerator::MergeListGroup /*{{{*/ +bool pkgCacheGenerator::MergeListGroup(ListParser &List, std::string const &GrpName) +{ + pkgCache::GrpIterator Grp = Cache.FindGrp(GrpName); + // a group has no data on it's own, only packages have it but these + // stanzas like this come from Translation- files to add descriptions, + // but without a version we don't need a description for it… + if (Grp.end() == true) + return true; + Dynamic<pkgCache::GrpIterator> DynGrp(Grp); + + pkgCache::PkgIterator Pkg; + Dynamic<pkgCache::PkgIterator> DynPkg(Pkg); + for (Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg)) + if (MergeListPackage(List, Pkg) == false) + return false; + + return true; +} + /*}}}*/ +// CacheGenerator::MergeListPackage /*{{{*/ +bool pkgCacheGenerator::MergeListPackage(ListParser &List, pkgCache::PkgIterator &Pkg) +{ + // we first process the package, then the descriptions + // (for deb this package processing is in fact a no-op) + pkgCache::VerIterator Ver(Cache); + Dynamic<pkgCache::VerIterator> DynVer(Ver); + if (List.UsePackage(Pkg, Ver) == false) + return _error->Error(_("Error occurred while processing %s (%s%d)"), + Pkg.Name(), "UsePackage", 1); + + // Find the right version to write the description + StringView CurMd5 = List.Description_md5(); + std::vector<std::string> availDesc = List.AvailableDescriptionLanguages(); + for (Ver = Pkg.VersionList(); Ver.end() == false; ++Ver) + { + pkgCache::DescIterator VerDesc = Ver.DescriptionList(); + + // a version can only have one md5 describing it + if (VerDesc.end() == true || Cache.ViewString(VerDesc->md5sum) != CurMd5) + continue; + + map_stringitem_t md5idx = VerDesc->md5sum; + for (std::vector<std::string>::const_iterator CurLang = availDesc.begin(); CurLang != availDesc.end(); ++CurLang) + { + // don't add a new description if we have one for the given + // md5 && language + if (IsDuplicateDescription(Cache, VerDesc, CurMd5, *CurLang) == true) + continue; + + AddNewDescription(List, Ver, *CurLang, CurMd5, md5idx); + } + + // we can stop here as all "same" versions will share the description + break; + } + + return true; +} + /*}}}*/ +// CacheGenerator::MergeListVersion /*{{{*/ +bool pkgCacheGenerator::MergeListVersion(ListParser &List, pkgCache::PkgIterator &Pkg, + APT::StringView const &Version, pkgCache::VerIterator* &OutVer) +{ + pkgCache::VerIterator Ver = Pkg.VersionList(); + Dynamic<pkgCache::VerIterator> DynVer(Ver); + map_pointer_t *LastVer = &Pkg->VersionList; + void const * oldMap = Map.Data(); + + unsigned short const Hash = List.VersionHash(); + if (Ver.end() == false) + { + /* We know the list is sorted so we use that fact in the search. + Insertion of new versions is done with correct sorting */ + int Res = 1; + for (; Ver.end() == false; LastVer = &Ver->NextVer, ++Ver) + { + char const * const VerStr = Ver.VerStr(); + Res = Cache.VS->DoCmpVersion(Version.data(), Version.data() + Version.length(), + VerStr, VerStr + strlen(VerStr)); + // Version is higher as current version - insert here + if (Res > 0) + break; + // Versionstrings are equal - is hash also equal? + if (Res == 0) + { + if (List.SameVersion(Hash, Ver) == true) + break; + // sort (volatile) sources above not-sources like the status file + if (CurrentFile == nullptr || (CurrentFile->Flags & pkgCache::Flag::NotSource) == 0) + { + auto VF = Ver.FileList(); + for (; VF.end() == false; ++VF) + if (VF.File().Flagged(pkgCache::Flag::NotSource) == false) + break; + if (VF.end() == true) + break; + } + } + // proceed with the next till we have either the right + // or we found another version (which will be lower) + } + + /* We already have a version for this item, record that we saw it */ + if (Res == 0 && Ver.end() == false && Ver->Hash == Hash) + { + if (List.UsePackage(Pkg,Ver) == false) + return _error->Error(_("Error occurred while processing %s (%s%d)"), + Pkg.Name(), "UsePackage", 2); + + if (NewFileVer(Ver,List) == false) + return _error->Error(_("Error occurred while processing %s (%s%d)"), + Pkg.Name(), "NewFileVer", 1); + + // Read only a single record and return + if (OutVer != 0) + { + *OutVer = Ver; + return true; + } + + return true; + } + } + + // Add a new version + map_pointer_t const verindex = NewVersion(Ver, Version, Pkg.Index(), Hash, *LastVer); + if (unlikely(verindex == 0)) + return _error->Error(_("Error occurred while processing %s (%s%d)"), + Pkg.Name(), "NewVersion", 1); + + if (oldMap != Map.Data()) + LastVer += static_cast<map_pointer_t const *>(Map.Data()) - static_cast<map_pointer_t const *>(oldMap); + *LastVer = verindex; + + if (unlikely(List.NewVersion(Ver) == false)) + return _error->Error(_("Error occurred while processing %s (%s%d)"), + Pkg.Name(), "NewVersion", 2); + + if (unlikely(List.UsePackage(Pkg,Ver) == false)) + return _error->Error(_("Error occurred while processing %s (%s%d)"), + Pkg.Name(), "UsePackage", 3); + + if (unlikely(NewFileVer(Ver,List) == false)) + return _error->Error(_("Error occurred while processing %s (%s%d)"), + Pkg.Name(), "NewFileVer", 2); + + pkgCache::GrpIterator Grp = Pkg.Group(); + Dynamic<pkgCache::GrpIterator> DynGrp(Grp); + + /* If it is the first version of this package we need to add implicit + Multi-Arch dependencies to all other package versions in the group now - + otherwise we just add them for this new version */ + if (Pkg.VersionList()->NextVer == 0) + { + pkgCache::PkgIterator P = Grp.PackageList(); + Dynamic<pkgCache::PkgIterator> DynP(P); + for (; P.end() != true; P = Grp.NextPkg(P)) + { + if (P->ID == Pkg->ID) + continue; + pkgCache::VerIterator V = P.VersionList(); + Dynamic<pkgCache::VerIterator> DynV(V); + for (; V.end() != true; ++V) + if (unlikely(AddImplicitDepends(V, Pkg) == false)) + return _error->Error(_("Error occurred while processing %s (%s%d)"), + Pkg.Name(), "AddImplicitDepends", 1); + } + } + if (unlikely(AddImplicitDepends(Grp, Pkg, Ver) == false)) + return _error->Error(_("Error occurred while processing %s (%s%d)"), + Pkg.Name(), "AddImplicitDepends", 2); + + // Read only a single record and return + if (OutVer != 0) + { + *OutVer = Ver; + return true; + } + + /* Record the Description(s) based on their master md5sum */ + StringView CurMd5 = List.Description_md5(); + + /* Before we add a new description we first search in the group for + a version with a description of the same MD5 - if so we reuse this + description group instead of creating our own for this version */ + for (pkgCache::PkgIterator P = Grp.PackageList(); + P.end() == false; P = Grp.NextPkg(P)) + { + for (pkgCache::VerIterator V = P.VersionList(); + V.end() == false; ++V) + { + if (V->DescriptionList == 0 || Cache.ViewString(V.DescriptionList()->md5sum) != CurMd5) + continue; + Ver->DescriptionList = V->DescriptionList; + } + } + + // We haven't found reusable descriptions, so add the first description(s) + map_stringitem_t md5idx = Ver->DescriptionList == 0 ? 0 : Ver.DescriptionList()->md5sum; + std::vector<std::string> availDesc = List.AvailableDescriptionLanguages(); + for (std::vector<std::string>::const_iterator CurLang = availDesc.begin(); CurLang != availDesc.end(); ++CurLang) + if (AddNewDescription(List, Ver, *CurLang, CurMd5, md5idx) == false) + return false; + return true; +} + /*}}}*/ +bool pkgCacheGenerator::AddNewDescription(ListParser &List, pkgCache::VerIterator &Ver, std::string const &lang, APT::StringView CurMd5, map_stringitem_t &md5idx) /*{{{*/ +{ + pkgCache::DescIterator Desc; + Dynamic<pkgCache::DescIterator> DynDesc(Desc); + + map_pointer_t const descindex = NewDescription(Desc, lang, CurMd5, md5idx); + if (unlikely(descindex == 0)) + return _error->Error(_("Error occurred while processing %s (%s%d)"), + Ver.ParentPkg().Name(), "NewDescription", 1); + + md5idx = Desc->md5sum; + Desc->ParentPkg = Ver.ParentPkg().Index(); + + // we add at the end, so that the start is constant as we need + // that to be able to efficiently share these lists + pkgCache::DescIterator VerDesc = Ver.DescriptionList(); // old value might be invalid after ReMap + for (;VerDesc.end() == false && VerDesc->NextDesc != 0; ++VerDesc); + map_pointer_t * const LastNextDesc = (VerDesc.end() == true) ? &Ver->DescriptionList : &VerDesc->NextDesc; + *LastNextDesc = descindex; + + if (NewFileDesc(Desc,List) == false) + return _error->Error(_("Error occurred while processing %s (%s%d)"), + Ver.ParentPkg().Name(), "NewFileDesc", 1); + + return true; +} + /*}}}*/ + /*}}}*/ +// CacheGenerator::NewGroup - Add a new group /*{{{*/ +// --------------------------------------------------------------------- +/* This creates a new group structure and adds it to the hash table */ +bool pkgCacheGenerator::NewGroup(pkgCache::GrpIterator &Grp, StringView Name) +{ + Dynamic<StringView> DName(Name); + Grp = Cache.FindGrp(Name); + if (Grp.end() == false) + return true; + + // Get a structure + map_pointer_t const Group = AllocateInMap(sizeof(pkgCache::Group)); + if (unlikely(Group == 0)) + return false; + + Grp = pkgCache::GrpIterator(Cache, Cache.GrpP + Group); + map_stringitem_t const idxName = StoreString(PKGNAME, Name); + if (unlikely(idxName == 0)) + return false; + Grp->Name = idxName; + + // Insert it into the hash table + unsigned long const Hash = Cache.Hash(Name); + map_pointer_t *insertAt = &Cache.HeaderP->GrpHashTableP()[Hash]; + + while (*insertAt != 0 && StringViewCompareFast(Name, Cache.ViewString((Cache.GrpP + *insertAt)->Name)) > 0) + insertAt = &(Cache.GrpP + *insertAt)->Next; + Grp->Next = *insertAt; + *insertAt = Group; + + Grp->ID = Cache.HeaderP->GroupCount++; + return true; +} + /*}}}*/ +// CacheGenerator::NewPackage - Add a new package /*{{{*/ +// --------------------------------------------------------------------- +/* This creates a new package structure and adds it to the hash table */ +bool pkgCacheGenerator::NewPackage(pkgCache::PkgIterator &Pkg, StringView Name, + StringView Arch) { + pkgCache::GrpIterator Grp; + Dynamic<StringView> DName(Name); + Dynamic<StringView> DArch(Arch); + Dynamic<pkgCache::GrpIterator> DynGrp(Grp); + if (unlikely(NewGroup(Grp, Name) == false)) + return false; + + Pkg = Grp.FindPkg(Arch); + if (Pkg.end() == false) + return true; + + // Get a structure + map_pointer_t const Package = AllocateInMap(sizeof(pkgCache::Package)); + if (unlikely(Package == 0)) + return false; + Pkg = pkgCache::PkgIterator(Cache,Cache.PkgP + Package); + + // Set the name, arch and the ID + APT_IGNORE_DEPRECATED_PUSH + Pkg->Name = Grp->Name; + APT_IGNORE_DEPRECATED_POP + Pkg->Group = Grp.Index(); + // all is mapped to the native architecture + map_stringitem_t const idxArch = (Arch == "all") ? Cache.HeaderP->Architecture : StoreString(MIXED, Arch); + if (unlikely(idxArch == 0)) + return false; + Pkg->Arch = idxArch; + Pkg->ID = Cache.HeaderP->PackageCount++; + + // Insert the package into our package list + if (Grp->FirstPackage == 0) // the group is new + { + Grp->FirstPackage = Package; + // Insert it into the hash table + map_id_t const Hash = Cache.Hash(Name); + map_pointer_t *insertAt = &Cache.HeaderP->PkgHashTableP()[Hash]; + while (*insertAt != 0 && StringViewCompareFast(Name, Cache.ViewString((Cache.GrpP + (Cache.PkgP + *insertAt)->Group)->Name)) > 0) + insertAt = &(Cache.PkgP + *insertAt)->NextPackage; + Pkg->NextPackage = *insertAt; + *insertAt = Package; + } + else // Group the Packages together + { + // if sibling is provided by another package, this one is too + { + pkgCache::PkgIterator const M = Grp.FindPreferredPkg(false); // native or any foreign pkg will do + if (M.end() == false) { + pkgCache::PrvIterator Prv; + Dynamic<pkgCache::PrvIterator> DynPrv(Prv); + for (Prv = M.ProvidesList(); Prv.end() == false; ++Prv) + { + if ((Prv->Flags & pkgCache::Flag::ArchSpecific) != 0) + continue; + pkgCache::VerIterator Ver = Prv.OwnerVer(); + Dynamic<pkgCache::VerIterator> DynVer(Ver); + if ((Ver->MultiArch & pkgCache::Version::Allowed) == pkgCache::Version::Allowed || + ((Ver->MultiArch & pkgCache::Version::Foreign) == pkgCache::Version::Foreign && + (Prv->Flags & pkgCache::Flag::MultiArchImplicit) == 0)) + { + if (APT::Configuration::checkArchitecture(Ver.ParentPkg().Arch()) == false) + continue; + if (NewProvides(Ver, Pkg, Prv->ProvideVersion, Prv->Flags) == false) + return false; + } + } + } + } + // let M-A:foreign package siblings provide this package + { + pkgCache::PkgIterator P; + pkgCache::VerIterator Ver; + Dynamic<pkgCache::PkgIterator> DynP(P); + Dynamic<pkgCache::VerIterator> DynVer(Ver); + + for (P = Grp.PackageList(); P.end() == false; P = Grp.NextPkg(P)) + { + if (APT::Configuration::checkArchitecture(P.Arch()) == false) + continue; + for (Ver = P.VersionList(); Ver.end() == false; ++Ver) + if ((Ver->MultiArch & pkgCache::Version::Foreign) == pkgCache::Version::Foreign) + if (NewProvides(Ver, Pkg, Ver->VerStr, pkgCache::Flag::MultiArchImplicit) == false) + return false; + } + } + // and negative dependencies, don't forget negative dependencies + { + pkgCache::PkgIterator const M = Grp.FindPreferredPkg(false); + if (M.end() == false) { + pkgCache::DepIterator Dep; + Dynamic<pkgCache::DepIterator> DynDep(Dep); + for (Dep = M.RevDependsList(); Dep.end() == false; ++Dep) + { + if ((Dep->CompareOp & (pkgCache::Dep::ArchSpecific | pkgCache::Dep::MultiArchImplicit)) != 0) + continue; + if (Dep->Type != pkgCache::Dep::DpkgBreaks && Dep->Type != pkgCache::Dep::Conflicts && + Dep->Type != pkgCache::Dep::Replaces) + continue; + pkgCache::VerIterator Ver = Dep.ParentVer(); + Dynamic<pkgCache::VerIterator> DynVer(Ver); + map_pointer_t * unused = NULL; + if (NewDepends(Pkg, Ver, Dep->Version, Dep->CompareOp, Dep->Type, unused) == false) + return false; + } + } + } + + // this package is the new last package + pkgCache::PkgIterator LastPkg(Cache, Cache.PkgP + Grp->LastPackage); + Pkg->NextPackage = LastPkg->NextPackage; + LastPkg->NextPackage = Package; + } + Grp->LastPackage = Package; + + // lazy-create foo (of amd64) provides foo:amd64 at the time we first need it + if (Arch == "any") + { + size_t const found = Name.rfind(':'); + StringView ArchA = Name.substr(found + 1); + if (ArchA != "any") + { + // ArchA is used inside the loop which might remap (NameA is not used) + Dynamic<StringView> DynArchA(ArchA); + StringView NameA = Name.substr(0, found); + pkgCache::PkgIterator PkgA = Cache.FindPkg(NameA, ArchA); + Dynamic<pkgCache::PkgIterator> DynPkgA(PkgA); + if (PkgA.end()) + { + Dynamic<StringView> DynNameA(NameA); + if (NewPackage(PkgA, NameA, ArchA) == false) + return false; + } + if (unlikely(PkgA.end())) + return _error->Fatal("NewPackage was successful for %s:%s," + "but the package doesn't exist anyhow!", + NameA.to_string().c_str(), ArchA.to_string().c_str()); + else + { + pkgCache::PrvIterator Prv = PkgA.ProvidesList(); + for (; Prv.end() == false; ++Prv) + { + if (Prv.IsMultiArchImplicit()) + continue; + pkgCache::VerIterator V = Prv.OwnerVer(); + if (ArchA != V.ParentPkg().Arch()) + continue; + if (NewProvides(V, Pkg, V->VerStr, pkgCache::Flag::MultiArchImplicit | pkgCache::Flag::ArchSpecific) == false) + return false; + } + pkgCache::VerIterator V = PkgA.VersionList(); + Dynamic<pkgCache::VerIterator> DynV(V); + for (; V.end() == false; ++V) + { + if (NewProvides(V, Pkg, V->VerStr, pkgCache::Flag::MultiArchImplicit | pkgCache::Flag::ArchSpecific) == false) + return false; + } + } + } + } + return true; +} + /*}}}*/ +// CacheGenerator::AddImplicitDepends /*{{{*/ +bool pkgCacheGenerator::AddImplicitDepends(pkgCache::GrpIterator &G, + pkgCache::PkgIterator &P, + pkgCache::VerIterator &V) +{ + APT::StringView Arch = P.Arch() == NULL ? "" : P.Arch(); + Dynamic<APT::StringView> DynArch(Arch); + map_pointer_t *OldDepLast = NULL; + /* MultiArch handling introduces a lot of implicit Dependencies: + - MultiArch: same → Co-Installable if they have the same version + - All others conflict with all other group members */ + bool const coInstall = ((V->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same); + pkgCache::PkgIterator D = G.PackageList(); + Dynamic<pkgCache::PkgIterator> DynD(D); + map_stringitem_t const VerStrIdx = V->VerStr; + for (; D.end() != true; D = G.NextPkg(D)) + { + if (Arch == D.Arch() || D->VersionList == 0) + continue; + /* We allow only one installed arch at the time + per group, therefore each group member conflicts + with all other group members */ + if (coInstall == true) + { + // Replaces: ${self}:other ( << ${binary:Version}) + NewDepends(D, V, VerStrIdx, + pkgCache::Dep::Less | pkgCache::Dep::MultiArchImplicit, pkgCache::Dep::Replaces, + OldDepLast); + // Breaks: ${self}:other (!= ${binary:Version}) + NewDepends(D, V, VerStrIdx, + pkgCache::Dep::NotEquals | pkgCache::Dep::MultiArchImplicit, pkgCache::Dep::DpkgBreaks, + OldDepLast); + } else { + // Conflicts: ${self}:other + NewDepends(D, V, 0, + pkgCache::Dep::NoOp | pkgCache::Dep::MultiArchImplicit, pkgCache::Dep::Conflicts, + OldDepLast); + } + } + return true; +} +bool pkgCacheGenerator::AddImplicitDepends(pkgCache::VerIterator &V, + pkgCache::PkgIterator &D) +{ + /* MultiArch handling introduces a lot of implicit Dependencies: + - MultiArch: same → Co-Installable if they have the same version + - All others conflict with all other group members */ + map_pointer_t *OldDepLast = NULL; + bool const coInstall = ((V->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same); + if (coInstall == true) + { + map_stringitem_t const VerStrIdx = V->VerStr; + // Replaces: ${self}:other ( << ${binary:Version}) + NewDepends(D, V, VerStrIdx, + pkgCache::Dep::Less | pkgCache::Dep::MultiArchImplicit, pkgCache::Dep::Replaces, + OldDepLast); + // Breaks: ${self}:other (!= ${binary:Version}) + NewDepends(D, V, VerStrIdx, + pkgCache::Dep::NotEquals | pkgCache::Dep::MultiArchImplicit, pkgCache::Dep::DpkgBreaks, + OldDepLast); + } else { + // Conflicts: ${self}:other + NewDepends(D, V, 0, + pkgCache::Dep::NoOp | pkgCache::Dep::MultiArchImplicit, pkgCache::Dep::Conflicts, + OldDepLast); + } + return true; +} + + /*}}}*/ +// CacheGenerator::NewFileVer - Create a new File<->Version association /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgCacheGenerator::NewFileVer(pkgCache::VerIterator &Ver, + ListParser &List) +{ + if (CurrentFile == nullptr) + return true; + + // Get a structure + map_pointer_t const VerFile = AllocateInMap(sizeof(pkgCache::VerFile)); + if (VerFile == 0) + return false; + + pkgCache::VerFileIterator VF(Cache,Cache.VerFileP + VerFile); + VF->File = CurrentFile - Cache.PkgFileP; + + // Link it to the end of the list + map_pointer_t *Last = &Ver->FileList; + for (pkgCache::VerFileIterator V = Ver.FileList(); V.end() == false; ++V) + Last = &V->NextFile; + VF->NextFile = *Last; + *Last = VF.Index(); + + VF->Offset = List.Offset(); + VF->Size = List.Size(); + if (Cache.HeaderP->MaxVerFileSize < VF->Size) + Cache.HeaderP->MaxVerFileSize = VF->Size; + Cache.HeaderP->VerFileCount++; + + return true; +} + /*}}}*/ +// CacheGenerator::NewVersion - Create a new Version /*{{{*/ +// --------------------------------------------------------------------- +/* This puts a version structure in the linked list */ +map_pointer_t pkgCacheGenerator::NewVersion(pkgCache::VerIterator &Ver, + APT::StringView const &VerStr, + map_pointer_t const ParentPkg, + unsigned short const Hash, + map_pointer_t const Next) +{ + // Get a structure + map_pointer_t const Version = AllocateInMap(sizeof(pkgCache::Version)); + if (Version == 0) + return 0; + + // Fill it in + Ver = pkgCache::VerIterator(Cache,Cache.VerP + Version); + //Dynamic<pkgCache::VerIterator> DynV(Ver); // caller MergeListVersion already takes care of it + Ver->NextVer = Next; + Ver->ParentPkg = ParentPkg; + Ver->Hash = Hash; + Ver->ID = Cache.HeaderP->VersionCount++; + + // try to find the version string in the group for reuse + pkgCache::PkgIterator Pkg = Ver.ParentPkg(); + pkgCache::GrpIterator Grp = Pkg.Group(); + if (Pkg.end() == false && Grp.end() == false) + { + for (pkgCache::PkgIterator P = Grp.PackageList(); P.end() == false; P = Grp.NextPkg(P)) + { + if (Pkg == P) + continue; + for (pkgCache::VerIterator V = P.VersionList(); V.end() == false; ++V) + { + int const cmp = strncmp(V.VerStr(), VerStr.data(), VerStr.length()); + if (cmp == 0 && V.VerStr()[VerStr.length()] == '\0') + { + Ver->VerStr = V->VerStr; + return Version; + } + else if (cmp < 0) + break; + } + } + } + // haven't found the version string, so create + map_stringitem_t const idxVerStr = StoreString(VERSIONNUMBER, VerStr); + if (unlikely(idxVerStr == 0)) + return 0; + Ver->VerStr = idxVerStr; + return Version; +} + /*}}}*/ +// CacheGenerator::NewFileDesc - Create a new File<->Desc association /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgCacheGenerator::NewFileDesc(pkgCache::DescIterator &Desc, + ListParser &List) +{ + if (CurrentFile == nullptr) + return true; + + // Get a structure + map_pointer_t const DescFile = AllocateInMap(sizeof(pkgCache::DescFile)); + if (DescFile == 0) + return false; + + pkgCache::DescFileIterator DF(Cache,Cache.DescFileP + DescFile); + DF->File = CurrentFile - Cache.PkgFileP; + + // Link it to the end of the list + map_pointer_t *Last = &Desc->FileList; + for (pkgCache::DescFileIterator D = Desc.FileList(); D.end() == false; ++D) + Last = &D->NextFile; + + DF->NextFile = *Last; + *Last = DF.Index(); + + DF->Offset = List.Offset(); + DF->Size = List.Size(); + if (Cache.HeaderP->MaxDescFileSize < DF->Size) + Cache.HeaderP->MaxDescFileSize = DF->Size; + Cache.HeaderP->DescFileCount++; + + return true; +} + /*}}}*/ +// CacheGenerator::NewDescription - Create a new Description /*{{{*/ +// --------------------------------------------------------------------- +/* This puts a description structure in the linked list */ +map_pointer_t pkgCacheGenerator::NewDescription(pkgCache::DescIterator &Desc, + const string &Lang, + APT::StringView md5sum, + map_stringitem_t const idxmd5str) +{ + // Get a structure + map_pointer_t const Description = AllocateInMap(sizeof(pkgCache::Description)); + if (Description == 0) + return 0; + + // Fill it in + Desc = pkgCache::DescIterator(Cache,Cache.DescP + Description); + Desc->ID = Cache.HeaderP->DescriptionCount++; + map_stringitem_t const idxlanguage_code = StoreString(MIXED, Lang); + if (unlikely(idxlanguage_code == 0)) + return 0; + Desc->language_code = idxlanguage_code; + + if (idxmd5str != 0) + Desc->md5sum = idxmd5str; + else + { + map_stringitem_t const idxmd5sum = WriteStringInMap(md5sum); + if (unlikely(idxmd5sum == 0)) + return 0; + Desc->md5sum = idxmd5sum; + } + + return Description; +} + /*}}}*/ +// CacheGenerator::NewDepends - Create a dependency element /*{{{*/ +// --------------------------------------------------------------------- +/* This creates a dependency element in the tree. It is linked to the + version and to the package that it is pointing to. */ +bool pkgCacheGenerator::NewDepends(pkgCache::PkgIterator &Pkg, + pkgCache::VerIterator &Ver, + map_pointer_t const Version, + uint8_t const Op, + uint8_t const Type, + map_pointer_t* &OldDepLast) +{ + void const * const oldMap = Map.Data(); + // Get a structure + map_pointer_t const Dependency = AllocateInMap(sizeof(pkgCache::Dependency)); + if (unlikely(Dependency == 0)) + return false; + + bool isDuplicate = false; + map_pointer_t DependencyData = 0; + map_pointer_t PreviousData = 0; + if (Pkg->RevDepends != 0) + { + pkgCache::Dependency const * const L = Cache.DepP + Pkg->RevDepends; + DependencyData = L->DependencyData; + do { + pkgCache::DependencyData const * const D = Cache.DepDataP + DependencyData; + if (Version > D->Version) + break; + if (D->Version == Version && D->Type == Type && D->CompareOp == Op) + { + isDuplicate = true; + break; + } + PreviousData = DependencyData; + DependencyData = D->NextData; + } while (DependencyData != 0); + } + + if (isDuplicate == false) + { + DependencyData = AllocateInMap(sizeof(pkgCache::DependencyData)); + if (unlikely(DependencyData == 0)) + return false; + } + + pkgCache::Dependency * Link = Cache.DepP + Dependency; + Link->ParentVer = Ver.Index(); + Link->DependencyData = DependencyData; + Link->ID = Cache.HeaderP->DependsCount++; + + pkgCache::DepIterator Dep(Cache, Link); + if (isDuplicate == false) + { + Dep->Type = Type; + Dep->CompareOp = Op; + Dep->Version = Version; + Dep->Package = Pkg.Index(); + ++Cache.HeaderP->DependsDataCount; + if (PreviousData != 0) + { + pkgCache::DependencyData * const D = Cache.DepDataP + PreviousData; + Dep->NextData = D->NextData; + D->NextData = DependencyData; + } + else if (Pkg->RevDepends != 0) + { + pkgCache::Dependency const * const D = Cache.DepP + Pkg->RevDepends; + Dep->NextData = D->DependencyData; + } + } + + if (isDuplicate == true || PreviousData != 0) + { + pkgCache::Dependency * const L = Cache.DepP + Pkg->RevDepends; + Link->NextRevDepends = L->NextRevDepends; + L->NextRevDepends = Dependency; + } + else + { + Link->NextRevDepends = Pkg->RevDepends; + Pkg->RevDepends = Dependency; + } + + + // Do we know where to link the Dependency to? + if (OldDepLast == NULL) + { + OldDepLast = &Ver->DependsList; + for (pkgCache::DepIterator D = Ver.DependsList(); D.end() == false; ++D) + OldDepLast = &D->NextDepends; + } else if (oldMap != Map.Data()) + OldDepLast += static_cast<map_pointer_t const *>(Map.Data()) - static_cast<map_pointer_t const *>(oldMap); + + Dep->NextDepends = *OldDepLast; + *OldDepLast = Dependency; + OldDepLast = &Dep->NextDepends; + return true; +} + /*}}}*/ +// ListParser::NewDepends - Create the environment for a new dependency /*{{{*/ +// --------------------------------------------------------------------- +/* This creates a Group and the Package to link this dependency to if + needed and handles also the caching of the old endpoint */ +bool pkgCacheListParser::NewDepends(pkgCache::VerIterator &Ver, + StringView PackageName, + StringView Arch, + StringView Version, + uint8_t const Op, + uint8_t const Type) +{ + pkgCache::GrpIterator Grp; + Dynamic<pkgCache::GrpIterator> DynGrp(Grp); + Dynamic<StringView> DynPackageName(PackageName); + Dynamic<StringView> DynArch(Arch); + Dynamic<StringView> DynVersion(Version); + if (unlikely(Owner->NewGroup(Grp, PackageName) == false)) + return false; + + map_stringitem_t idxVersion = 0; + if (Version.empty() == false) + { + int const CmpOp = Op & 0x0F; + // =-deps are used (79:1) for lockstep on same-source packages (e.g. data-packages) + if (CmpOp == pkgCache::Dep::Equals && Version == Ver.VerStr()) + idxVersion = Ver->VerStr; + + if (idxVersion == 0) + { + idxVersion = StoreString(pkgCacheGenerator::VERSIONNUMBER, Version); + if (unlikely(idxVersion == 0)) + return false; + } + } + + bool const isNegative = (Type == pkgCache::Dep::DpkgBreaks || + Type == pkgCache::Dep::Conflicts || + Type == pkgCache::Dep::Replaces); + + pkgCache::PkgIterator Pkg; + Dynamic<pkgCache::PkgIterator> DynPkg(Pkg); + if (isNegative == false || (Op & pkgCache::Dep::ArchSpecific) == pkgCache::Dep::ArchSpecific || Grp->FirstPackage == 0) + { + // Locate the target package + Pkg = Grp.FindPkg(Arch); + if (Pkg.end() == true) { + if (unlikely(Owner->NewPackage(Pkg, PackageName, Arch) == false)) + return false; + } + + /* Caching the old end point speeds up generation substantially */ + if (OldDepVer != Ver) { + OldDepLast = NULL; + OldDepVer = Ver; + } + + return Owner->NewDepends(Pkg, Ver, idxVersion, Op, Type, OldDepLast); + } + else + { + /* Caching the old end point speeds up generation substantially */ + if (OldDepVer != Ver) { + OldDepLast = NULL; + OldDepVer = Ver; + } + + for (Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg)) + { + if (Owner->NewDepends(Pkg, Ver, idxVersion, Op, Type, OldDepLast) == false) + return false; + } + } + return true; +} + /*}}}*/ +// ListParser::NewProvides - Create a Provides element /*{{{*/ +bool pkgCacheListParser::NewProvides(pkgCache::VerIterator &Ver, + StringView PkgName, + StringView PkgArch, + StringView Version, + uint8_t const Flags) +{ + pkgCache const &Cache = Owner->Cache; + Dynamic<StringView> DynPkgName(PkgName); + Dynamic<StringView> DynArch(PkgArch); + Dynamic<StringView> DynVersion(Version); + + // We do not add self referencing provides + if (Ver.ParentPkg().Name() == PkgName && (PkgArch == Ver.ParentPkg().Arch() || + (PkgArch == "all" && strcmp((Cache.StrP + Cache.HeaderP->Architecture), Ver.ParentPkg().Arch()) == 0)) && + (Version.empty() || Version == Ver.VerStr())) + return true; + + // Locate the target package + pkgCache::PkgIterator Pkg; + Dynamic<pkgCache::PkgIterator> DynPkg(Pkg); + if (unlikely(Owner->NewPackage(Pkg,PkgName, PkgArch) == false)) + return false; + + map_stringitem_t idxProvideVersion = 0; + if (Version.empty() == false) { + idxProvideVersion = StoreString(pkgCacheGenerator::VERSIONNUMBER, Version); + if (unlikely(idxProvideVersion == 0)) + return false; + } + return Owner->NewProvides(Ver, Pkg, idxProvideVersion, Flags); +} +bool pkgCacheGenerator::NewProvides(pkgCache::VerIterator &Ver, + pkgCache::PkgIterator &Pkg, + map_pointer_t const ProvideVersion, + uint8_t const Flags) +{ + // Get a structure + map_pointer_t const Provides = AllocateInMap(sizeof(pkgCache::Provides)); + if (unlikely(Provides == 0)) + return false; + ++Cache.HeaderP->ProvidesCount; + + // Fill it in + pkgCache::PrvIterator Prv(Cache,Cache.ProvideP + Provides,Cache.PkgP); + Prv->Version = Ver.Index(); + Prv->ProvideVersion = ProvideVersion; + Prv->Flags = Flags; + Prv->NextPkgProv = Ver->ProvidesList; + Ver->ProvidesList = Prv.Index(); + + // Link it to the package + Prv->ParentPkg = Pkg.Index(); + Prv->NextProvides = Pkg->ProvidesList; + Pkg->ProvidesList = Prv.Index(); + return true; +} + /*}}}*/ +// ListParser::NewProvidesAllArch - add provides for all architectures /*{{{*/ +bool pkgCacheListParser::NewProvidesAllArch(pkgCache::VerIterator &Ver, StringView Package, + StringView Version, uint8_t const Flags) { + pkgCache &Cache = Owner->Cache; + pkgCache::GrpIterator Grp = Cache.FindGrp(Package); + Dynamic<pkgCache::GrpIterator> DynGrp(Grp); + Dynamic<StringView> DynPackage(Package); + Dynamic<StringView> DynVersion(Version); + + if (Grp.end() == true) + return NewProvides(Ver, Package, Cache.NativeArch(), Version, Flags); + else + { + map_stringitem_t idxProvideVersion = 0; + if (Version.empty() == false) { + idxProvideVersion = StoreString(pkgCacheGenerator::VERSIONNUMBER, Version); + if (unlikely(idxProvideVersion == 0)) + return false; + } + + bool const isImplicit = (Flags & pkgCache::Flag::MultiArchImplicit) == pkgCache::Flag::MultiArchImplicit; + bool const isArchSpecific = (Flags & pkgCache::Flag::ArchSpecific) == pkgCache::Flag::ArchSpecific; + pkgCache::PkgIterator OwnerPkg = Ver.ParentPkg(); + Dynamic<pkgCache::PkgIterator> DynOwnerPkg(OwnerPkg); + pkgCache::PkgIterator Pkg; + Dynamic<pkgCache::PkgIterator> DynPkg(Pkg); + for (Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg)) + { + if (isImplicit && OwnerPkg == Pkg) + continue; + if (isArchSpecific == false && APT::Configuration::checkArchitecture(OwnerPkg.Arch()) == false) + continue; + if (Owner->NewProvides(Ver, Pkg, idxProvideVersion, Flags) == false) + return false; + } + } + return true; +} + /*}}}*/ +bool pkgCacheListParser::SameVersion(unsigned short const Hash, /*{{{*/ + pkgCache::VerIterator const &Ver) +{ + return Hash == Ver->Hash; +} + /*}}}*/ +// CacheGenerator::SelectReleaseFile - Select the current release file the indexes belong to /*{{{*/ +bool pkgCacheGenerator::SelectReleaseFile(const string &File,const string &Site, + unsigned long Flags) +{ + CurrentRlsFile = nullptr; + if (File.empty() && Site.empty()) + return true; + + // Get some space for the structure + map_pointer_t const idxFile = AllocateInMap(sizeof(*CurrentRlsFile)); + if (unlikely(idxFile == 0)) + return false; + CurrentRlsFile = Cache.RlsFileP + idxFile; + + // Fill it in + map_stringitem_t const idxFileName = WriteStringInMap(File); + map_stringitem_t const idxSite = StoreString(MIXED, Site); + if (unlikely(idxFileName == 0 || idxSite == 0)) + return false; + CurrentRlsFile->FileName = idxFileName; + CurrentRlsFile->Site = idxSite; + CurrentRlsFile->NextFile = Cache.HeaderP->RlsFileList; + CurrentRlsFile->Flags = Flags; + CurrentRlsFile->ID = Cache.HeaderP->ReleaseFileCount; + RlsFileName = File; + Cache.HeaderP->RlsFileList = CurrentRlsFile - Cache.RlsFileP; + Cache.HeaderP->ReleaseFileCount++; + + return true; +} + /*}}}*/ +// CacheGenerator::SelectFile - Select the current file being parsed /*{{{*/ +// --------------------------------------------------------------------- +/* This is used to select which file is to be associated with all newly + added versions. The caller is responsible for setting the IMS fields. */ +bool pkgCacheGenerator::SelectFile(std::string const &File, + pkgIndexFile const &Index, + std::string const &Architecture, + std::string const &Component, + unsigned long const Flags) +{ + CurrentFile = nullptr; + // Get some space for the structure + map_pointer_t const idxFile = AllocateInMap(sizeof(*CurrentFile)); + if (unlikely(idxFile == 0)) + return false; + CurrentFile = Cache.PkgFileP + idxFile; + + // Fill it in + map_stringitem_t const idxFileName = WriteStringInMap(File); + if (unlikely(idxFileName == 0)) + return false; + CurrentFile->FileName = idxFileName; + CurrentFile->NextFile = Cache.HeaderP->FileList; + CurrentFile->ID = Cache.HeaderP->PackageFileCount; + map_stringitem_t const idxIndexType = StoreString(MIXED, Index.GetType()->Label); + if (unlikely(idxIndexType == 0)) + return false; + CurrentFile->IndexType = idxIndexType; + if (Architecture.empty()) + CurrentFile->Architecture = 0; + else + { + map_stringitem_t const arch = StoreString(pkgCacheGenerator::MIXED, Architecture); + if (unlikely(arch == 0)) + return false; + CurrentFile->Architecture = arch; + } + map_stringitem_t const component = StoreString(pkgCacheGenerator::MIXED, Component); + if (unlikely(component == 0)) + return false; + CurrentFile->Component = component; + CurrentFile->Flags = Flags; + if (CurrentRlsFile != nullptr) + CurrentFile->Release = CurrentRlsFile - Cache.RlsFileP; + else + CurrentFile->Release = 0; + PkgFileName = File; + Cache.HeaderP->FileList = CurrentFile - Cache.PkgFileP; + Cache.HeaderP->PackageFileCount++; + + if (Progress != 0) + Progress->SubProgress(Index.Size()); + return true; +} + /*}}}*/ +// CacheGenerator::WriteUniqueString - Insert a unique string /*{{{*/ +// --------------------------------------------------------------------- +/* This is used to create handles to strings. Given the same text it + always returns the same number */ +map_stringitem_t pkgCacheGenerator::StoreString(enum StringType const type, const char *S, + unsigned int Size) +{ + auto strings = &strMixed; + switch(type) { + case MIXED: strings = &strMixed; break; + case PKGNAME: strings = &strPkgNames; break; + case VERSIONNUMBER: strings = &strVersions; break; + case SECTION: strings = &strSections; break; + default: _error->Fatal("Unknown enum type used for string storage of '%.*s'", Size, S); return 0; + } + + auto const item = strings->find({S, Size, nullptr, 0}); + if (item != strings->end()) + return item->item; + + map_stringitem_t const idxString = WriteStringInMap(S,Size); + strings->insert({nullptr, Size, this, idxString}); + return idxString; +} + /*}}}*/ +// CheckValidity - Check that a cache is up-to-date /*{{{*/ +// --------------------------------------------------------------------- +/* This just verifies that each file in the list of index files exists, + has matching attributes with the cache and the cache does not have + any extra files. */ +class APT_HIDDEN ScopedErrorRevert { +public: + ScopedErrorRevert() { _error->PushToStack(); } + ~ScopedErrorRevert() { _error->RevertToStack(); } +}; +static bool CheckValidity(FileFd &CacheFile, std::string const &CacheFileName, + pkgSourceList &List, + FileIterator const Start, + FileIterator const End, + MMap **OutMap = 0, + pkgCache **OutCache = 0) +{ + if (CacheFileName.empty()) + return false; + ScopedErrorRevert ser; + + bool const Debug = _config->FindB("Debug::pkgCacheGen", false); + // No file, certainly invalid + if (CacheFile.Open(CacheFileName, FileFd::ReadOnly, FileFd::None) == false) + { + if (Debug == true) + std::clog << "CacheFile " << CacheFileName << " doesn't exist" << std::endl; + return false; + } + + if (List.GetLastModifiedTime() > CacheFile.ModificationTime()) + { + if (Debug == true) + std::clog << "sources.list is newer than the cache" << std::endl; + return false; + } + + // Map it + std::unique_ptr<MMap> Map(new MMap(CacheFile,0)); + if (unlikely(Map->validData()) == false) + return false; + std::unique_ptr<pkgCache> CacheP(new pkgCache(Map.get())); + pkgCache &Cache = *CacheP.get(); + if (_error->PendingError() || Map->Size() == 0) + { + if (Debug == true) + std::clog << "Errors are pending or Map is empty() for " << CacheFileName << std::endl; + return false; + } + + std::unique_ptr<bool[]> RlsVisited(new bool[Cache.HeaderP->ReleaseFileCount]); + memset(RlsVisited.get(),0,sizeof(RlsVisited[0])*Cache.HeaderP->ReleaseFileCount); + std::vector<pkgIndexFile *> Files; + for (pkgSourceList::const_iterator i = List.begin(); i != List.end(); ++i) + { + if (Debug == true) + std::clog << "Checking RlsFile " << (*i)->Describe() << ": "; + pkgCache::RlsFileIterator const RlsFile = (*i)->FindInCache(Cache, true); + if (RlsFile.end() == true) + { + if (Debug == true) + std::clog << "FindInCache returned end-Pointer" << std::endl; + return false; + } + + RlsVisited[RlsFile->ID] = true; + if (Debug == true) + std::clog << "with ID " << RlsFile->ID << " is valid" << std::endl; + + std::vector <pkgIndexFile *> const * const Indexes = (*i)->GetIndexFiles(); + std::copy_if(Indexes->begin(), Indexes->end(), std::back_inserter(Files), + [](pkgIndexFile const * const I) { return I->HasPackages(); }); + } + for (unsigned I = 0; I != Cache.HeaderP->ReleaseFileCount; ++I) + if (RlsVisited[I] == false) + { + if (Debug == true) + std::clog << "RlsFile with ID" << I << " wasn't visited" << std::endl; + return false; + } + + std::copy(Start, End, std::back_inserter(Files)); + + /* Now we check every index file, see if it is in the cache, + verify the IMS data and check that it is on the disk too.. */ + std::unique_ptr<bool[]> Visited(new bool[Cache.HeaderP->PackageFileCount]); + memset(Visited.get(),0,sizeof(Visited[0])*Cache.HeaderP->PackageFileCount); + for (std::vector<pkgIndexFile *>::const_reverse_iterator PkgFile = Files.rbegin(); PkgFile != Files.rend(); ++PkgFile) + { + if (Debug == true) + std::clog << "Checking PkgFile " << (*PkgFile)->Describe() << ": "; + if ((*PkgFile)->Exists() == false) + { + if (Debug == true) + std::clog << "file doesn't exist" << std::endl; + continue; + } + + // FindInCache is also expected to do an IMS check. + pkgCache::PkgFileIterator File = (*PkgFile)->FindInCache(Cache); + if (File.end() == true) + { + if (Debug == true) + std::clog << "FindInCache returned end-Pointer" << std::endl; + return false; + } + + Visited[File->ID] = true; + if (Debug == true) + std::clog << "with ID " << File->ID << " is valid" << std::endl; + } + + for (unsigned I = 0; I != Cache.HeaderP->PackageFileCount; I++) + if (Visited[I] == false) + { + if (Debug == true) + std::clog << "PkgFile with ID" << I << " wasn't visited" << std::endl; + return false; + } + + if (_error->PendingError() == true) + { + if (Debug == true) + { + std::clog << "Validity failed because of pending errors:" << std::endl; + _error->DumpErrors(std::clog, GlobalError::DEBUG, false); + } + return false; + } + + if (OutMap != 0) + *OutMap = Map.release(); + if (OutCache != 0) + *OutCache = CacheP.release(); + return true; +} + /*}}}*/ +// ComputeSize - Compute the total size of a bunch of files /*{{{*/ +// --------------------------------------------------------------------- +/* Size is kind of an abstract notion that is only used for the progress + meter */ +static map_filesize_t ComputeSize(pkgSourceList const * const List, FileIterator Start,FileIterator End) +{ + map_filesize_t TotalSize = 0; + if (List != NULL) + { + for (pkgSourceList::const_iterator i = List->begin(); i != List->end(); ++i) + { + std::vector <pkgIndexFile *> *Indexes = (*i)->GetIndexFiles(); + for (std::vector<pkgIndexFile *>::const_iterator j = Indexes->begin(); j != Indexes->end(); ++j) + if ((*j)->HasPackages() == true) + TotalSize += (*j)->Size(); + } + } + + for (; Start < End; ++Start) + { + if ((*Start)->HasPackages() == false) + continue; + TotalSize += (*Start)->Size(); + } + return TotalSize; +} + /*}}}*/ +// BuildCache - Merge the list of index files into the cache /*{{{*/ +static bool BuildCache(pkgCacheGenerator &Gen, + OpProgress * const Progress, + map_filesize_t &CurrentSize,map_filesize_t TotalSize, + pkgSourceList const * const List, + FileIterator const Start, FileIterator const End) +{ + bool mergeFailure = false; + + auto const indexFileMerge = [&](pkgIndexFile * const I) { + if (I->HasPackages() == false || mergeFailure) + return; + + if (I->Exists() == false) + return; + + if (I->FindInCache(Gen.GetCache()).end() == false) + { + _error->Warning("Duplicate sources.list entry %s", + I->Describe().c_str()); + return; + } + + map_filesize_t const Size = I->Size(); + if (Progress != NULL) + Progress->OverallProgress(CurrentSize, TotalSize, Size, _("Reading package lists")); + CurrentSize += Size; + + if (I->Merge(Gen,Progress) == false) + mergeFailure = true; + }; + + if (List != NULL) + { + for (pkgSourceList::const_iterator i = List->begin(); i != List->end(); ++i) + { + if ((*i)->FindInCache(Gen.GetCache(), false).end() == false) + { + _error->Warning("Duplicate sources.list entry %s", + (*i)->Describe().c_str()); + continue; + } + + if ((*i)->Merge(Gen, Progress) == false) + return false; + + std::vector <pkgIndexFile *> *Indexes = (*i)->GetIndexFiles(); + if (Indexes != NULL) + std::for_each(Indexes->begin(), Indexes->end(), indexFileMerge); + if (mergeFailure) + return false; + } + } + + if (Start != End) + { + Gen.SelectReleaseFile("", ""); + std::for_each(Start, End, indexFileMerge); + if (mergeFailure) + return false; + } + return true; +} + /*}}}*/ +// CacheGenerator::MakeStatusCache - Construct the status cache /*{{{*/ +// --------------------------------------------------------------------- +/* This makes sure that the status cache (the cache that has all + index files from the sources list and all local ones) is ready + to be mmaped. If OutMap is not zero then a MMap object representing + the cache will be stored there. This is pretty much mandatory if you + are using AllowMem. AllowMem lets the function be run as non-root + where it builds the cache 'fast' into a memory buffer. */ +static DynamicMMap* CreateDynamicMMap(FileFd * const CacheF, unsigned long Flags) +{ + map_filesize_t const MapStart = _config->FindI("APT::Cache-Start", 24*1024*1024); + map_filesize_t const MapGrow = _config->FindI("APT::Cache-Grow", 1*1024*1024); + map_filesize_t const MapLimit = _config->FindI("APT::Cache-Limit", 0); + Flags |= MMap::Moveable; + if (_config->FindB("APT::Cache-Fallback", false) == true) + Flags |= MMap::Fallback; + if (CacheF != NULL) + return new DynamicMMap(*CacheF, Flags, MapStart, MapGrow, MapLimit); + else + return new DynamicMMap(Flags, MapStart, MapGrow, MapLimit); +} +static bool writeBackMMapToFile(pkgCacheGenerator * const Gen, DynamicMMap * const Map, + std::string const &FileName) +{ + FileFd SCacheF(FileName, FileFd::WriteAtomic); + if (SCacheF.IsOpen() == false || SCacheF.Failed()) + return false; + + fchmod(SCacheF.Fd(),0644); + + // Write out the main data + if (SCacheF.Write(Map->Data(),Map->Size()) == false) + return _error->Error(_("IO Error saving source cache")); + + // Write out the proper header + Gen->GetCache().HeaderP->Dirty = false; + Gen->GetCache().HeaderP->CacheFileSize = Gen->GetCache().CacheHash(); + if (SCacheF.Seek(0) == false || + SCacheF.Write(Map->Data(),sizeof(*Gen->GetCache().HeaderP)) == false) + return _error->Error(_("IO Error saving source cache")); + Gen->GetCache().HeaderP->Dirty = true; + return true; +} +static bool loadBackMMapFromFile(std::unique_ptr<pkgCacheGenerator> &Gen, + std::unique_ptr<DynamicMMap> &Map, OpProgress * const Progress, FileFd &CacheF) +{ + Map.reset(CreateDynamicMMap(NULL, 0)); + if (unlikely(Map->validData()) == false) + return false; + if (CacheF.IsOpen() == false || CacheF.Seek(0) == false || CacheF.Failed()) + return false; + _error->PushToStack(); + map_pointer_t const alloc = Map->RawAllocate(CacheF.Size()); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + if (alloc == 0 && newError) + return false; + if (CacheF.Read((unsigned char *)Map->Data() + alloc, CacheF.Size()) == false) + return false; + Gen.reset(new pkgCacheGenerator(Map.get(),Progress)); + return Gen->Start(); +} +bool pkgMakeStatusCache(pkgSourceList &List,OpProgress &Progress, + MMap **OutMap, bool AllowMem) + { return pkgCacheGenerator::MakeStatusCache(List, &Progress, OutMap, AllowMem); } +bool pkgCacheGenerator::MakeStatusCache(pkgSourceList &List,OpProgress *Progress, + MMap **OutMap,bool) +{ + return pkgCacheGenerator::MakeStatusCache(List, Progress, OutMap, nullptr, true); +} +bool pkgCacheGenerator::MakeStatusCache(pkgSourceList &List,OpProgress *Progress, + MMap **OutMap,pkgCache **OutCache, bool) +{ + // FIXME: deprecate the ignored AllowMem parameter + bool const Debug = _config->FindB("Debug::pkgCacheGen", false); + + std::vector<pkgIndexFile *> Files; + if (_system->AddStatusFiles(Files) == false) + return false; + + // Decide if we can write to the files.. + string const CacheFileName = _config->FindFile("Dir::Cache::pkgcache"); + string const SrcCacheFileName = _config->FindFile("Dir::Cache::srcpkgcache"); + + // ensure the cache directory exists + if (CacheFileName.empty() == false || SrcCacheFileName.empty() == false) + { + string dir = _config->FindDir("Dir::Cache"); + size_t const len = dir.size(); + if (len > 5 && dir.find("/apt/", len - 6, 5) == len - 5) + dir = dir.substr(0, len - 5); + if (CacheFileName.empty() == false) + CreateDirectory(dir, flNotFile(CacheFileName)); + if (SrcCacheFileName.empty() == false) + CreateDirectory(dir, flNotFile(SrcCacheFileName)); + } + + if (Progress != NULL) + Progress->OverallProgress(0,1,1,_("Reading package lists")); + + bool pkgcache_fine = false; + bool srcpkgcache_fine = false; + bool volatile_fine = List.GetVolatileFiles().empty(); + FileFd CacheFile; + if (CheckValidity(CacheFile, CacheFileName, List, Files.begin(), Files.end(), volatile_fine ? OutMap : NULL, + volatile_fine ? OutCache : NULL) == true) + { + if (Debug == true) + std::clog << "pkgcache.bin is valid - no need to build any cache" << std::endl; + pkgcache_fine = true; + srcpkgcache_fine = true; + } + + FileFd SrcCacheFile; + if (pkgcache_fine == false) + { + if (CheckValidity(SrcCacheFile, SrcCacheFileName, List, Files.end(), Files.end()) == true) + { + if (Debug == true) + std::clog << "srcpkgcache.bin is valid - it can be reused" << std::endl; + srcpkgcache_fine = true; + } + } + + if (volatile_fine == true && srcpkgcache_fine == true && pkgcache_fine == true) + { + if (Progress != NULL) + Progress->OverallProgress(1,1,1,_("Reading package lists")); + return true; + } + + bool Writeable = false; + if (srcpkgcache_fine == false || pkgcache_fine == false) + { + if (CacheFileName.empty() == false) + Writeable = access(flNotFile(CacheFileName).c_str(),W_OK) == 0; + else if (SrcCacheFileName.empty() == false) + Writeable = access(flNotFile(SrcCacheFileName).c_str(),W_OK) == 0; + + if (Debug == true) + std::clog << "Do we have write-access to the cache files? " << (Writeable ? "YES" : "NO") << std::endl; + } + + // At this point we know we need to construct something, so get storage ready + std::unique_ptr<DynamicMMap> Map(CreateDynamicMMap(NULL, 0)); + if (unlikely(Map->validData()) == false) + return false; + if (Debug == true) + std::clog << "Open memory Map (not filebased)" << std::endl; + + std::unique_ptr<pkgCacheGenerator> Gen{nullptr}; + map_filesize_t CurrentSize = 0; + std::vector<pkgIndexFile*> VolatileFiles = List.GetVolatileFiles(); + map_filesize_t TotalSize = ComputeSize(NULL, VolatileFiles.begin(), VolatileFiles.end()); + if (srcpkgcache_fine == true && pkgcache_fine == false) + { + if (Debug == true) + std::clog << "srcpkgcache.bin was valid - populate MMap with it" << std::endl; + if (loadBackMMapFromFile(Gen, Map, Progress, SrcCacheFile) == false) + return false; + srcpkgcache_fine = true; + TotalSize += ComputeSize(NULL, Files.begin(), Files.end()); + } + else if (srcpkgcache_fine == false) + { + if (Debug == true) + std::clog << "srcpkgcache.bin is NOT valid - rebuild" << std::endl; + Gen.reset(new pkgCacheGenerator(Map.get(),Progress)); + if (Gen->Start() == false) + return false; + + TotalSize += ComputeSize(&List, Files.begin(),Files.end()); + if (BuildCache(*Gen, Progress, CurrentSize, TotalSize, &List, + Files.end(),Files.end()) == false) + return false; + + if (Writeable == true && SrcCacheFileName.empty() == false) + if (writeBackMMapToFile(Gen.get(), Map.get(), SrcCacheFileName) == false) + return false; + } + + if (pkgcache_fine == false) + { + if (Debug == true) + std::clog << "Building status cache in pkgcache.bin now" << std::endl; + if (BuildCache(*Gen, Progress, CurrentSize, TotalSize, NULL, + Files.begin(), Files.end()) == false) + return false; + + if (Writeable == true && CacheFileName.empty() == false) + if (writeBackMMapToFile(Gen.get(), Map.get(), CacheFileName) == false) + return false; + } + + if (Debug == true) + std::clog << "Caches done. " << (volatile_fine ? "No volatile files, so we are done here." : "Now bring in the volatile files") << std::endl; + + if (volatile_fine == false) + { + if (Gen == nullptr) + { + if (Debug == true) + std::clog << "Populate new MMap with cachefile contents" << std::endl; + if (loadBackMMapFromFile(Gen, Map, Progress, CacheFile) == false) + return false; + } + + Files = List.GetVolatileFiles(); + if (BuildCache(*Gen, Progress, CurrentSize, TotalSize, NULL, + Files.begin(), Files.end()) == false) + return false; + } + + if (OutMap != nullptr) + *OutMap = Map.release(); + + if (Debug == true) + std::clog << "Everything is ready for shipping" << std::endl; + return true; +} + /*}}}*/ +// CacheGenerator::MakeOnlyStatusCache - Build only a status files cache/*{{{*/ +class APT_HIDDEN ScopedErrorMerge { +public: + ScopedErrorMerge() { _error->PushToStack(); } + ~ScopedErrorMerge() { _error->MergeWithStack(); } +}; +bool pkgMakeOnlyStatusCache(OpProgress &Progress,DynamicMMap **OutMap) + { return pkgCacheGenerator::MakeOnlyStatusCache(&Progress, OutMap); } +bool pkgCacheGenerator::MakeOnlyStatusCache(OpProgress *Progress,DynamicMMap **OutMap) +{ + std::vector<pkgIndexFile *> Files; + if (_system->AddStatusFiles(Files) == false) + return false; + + ScopedErrorMerge sem; + std::unique_ptr<DynamicMMap> Map(CreateDynamicMMap(NULL, 0)); + if (unlikely(Map->validData()) == false) + return false; + map_filesize_t CurrentSize = 0; + map_filesize_t TotalSize = 0; + TotalSize = ComputeSize(NULL, Files.begin(), Files.end()); + + // Build the status cache + if (Progress != NULL) + Progress->OverallProgress(0,1,1,_("Reading package lists")); + pkgCacheGenerator Gen(Map.get(),Progress); + if (Gen.Start() == false || _error->PendingError() == true) + return false; + if (BuildCache(Gen,Progress,CurrentSize,TotalSize, NULL, + Files.begin(), Files.end()) == false) + return false; + + if (_error->PendingError() == true) + return false; + *OutMap = Map.release(); + + return true; +} + /*}}}*/ +// IsDuplicateDescription /*{{{*/ +static bool IsDuplicateDescription(pkgCache &Cache, pkgCache::DescIterator Desc, + APT::StringView CurMd5, std::string const &CurLang) +{ + // Descriptions in the same link-list have all the same md5 + if (Desc.end() == true || Cache.ViewString(Desc->md5sum) != CurMd5) + return false; + for (; Desc.end() == false; ++Desc) + if (Desc.LanguageCode() == CurLang) + return true; + return false; +} + /*}}}*/ + +pkgCacheListParser::pkgCacheListParser() : Owner(NULL), OldDepLast(NULL), d(NULL) {} +pkgCacheListParser::~pkgCacheListParser() {} diff --git a/apt-pkg/pkgcachegen.h b/apt-pkg/pkgcachegen.h new file mode 100644 index 0000000..e11e97e --- /dev/null +++ b/apt-pkg/pkgcachegen.h @@ -0,0 +1,252 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Package Cache Generator - Generator for the cache structure. + + This builds the cache structure from the abstract package list parser. + Each archive source has it's own list parser that is instantiated by + the caller to provide data for the generator. + + Parts of the cache are created by this generator class while other + parts are created by the list parser. The list parser is responsible + for creating version, depends and provides structures, and some of + their contents + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_PKGCACHEGEN_H +#define PKGLIB_PKGCACHEGEN_H + +#include <apt-pkg/macros.h> +#include <apt-pkg/md5.h> +#include <apt-pkg/mmap.h> +#include <apt-pkg/pkgcache.h> + +#include <string> +#include <vector> +#if __cplusplus >= 201103L +#include <unordered_set> +#endif +#ifdef APT_PKG_EXPOSE_STRING_VIEW +#include <apt-pkg/string_view.h> +#endif + +class FileFd; +class pkgSourceList; +class OpProgress; +class pkgIndexFile; +class pkgCacheListParser; + +class APT_HIDDEN pkgCacheGenerator /*{{{*/ +{ +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN map_stringitem_t WriteStringInMap(APT::StringView String) { return WriteStringInMap(String.data(), String.size()); }; +#endif + APT_HIDDEN map_stringitem_t WriteStringInMap(const char *String); + APT_HIDDEN map_stringitem_t WriteStringInMap(const char *String, const unsigned long &Len); + APT_HIDDEN map_pointer_t AllocateInMap(const unsigned long &size); + + // Dirty hack for public users that do not use C++11 yet +#if __cplusplus >= 201103L + struct string_pointer { + const char *data_; + size_t size; + pkgCacheGenerator *generator; + map_stringitem_t item; + + const char *data() const { + return data_ != nullptr ? data_ : static_cast<char*>(generator->Map.Data()) + item; + } + + bool operator ==(string_pointer const &other) const { + return size == other.size && memcmp(data(), other.data(), size) == 0; + } + }; + struct hash { + uint32_t operator()(string_pointer const &that) const { + uint32_t Hash = 5381; + const char * const end = that.data() + that.size; + for (const char *I = that.data(); I != end; ++I) + Hash = 33 * Hash + *I; + return Hash; + } + }; + + std::unordered_set<string_pointer, hash> strMixed; + std::unordered_set<string_pointer, hash> strPkgNames; + std::unordered_set<string_pointer, hash> strVersions; + std::unordered_set<string_pointer, hash> strSections; +#endif + + friend class pkgCacheListParser; + typedef pkgCacheListParser ListParser; + + public: + + template<typename Iter> class Dynamic { + public: + static std::vector<Iter*> toReMap; + explicit Dynamic(Iter &I) { + toReMap.push_back(&I); + } + + ~Dynamic() { + toReMap.pop_back(); + } + +#if __cplusplus >= 201103L + Dynamic(const Dynamic&) = delete; + void operator=(const Dynamic&) = delete; +#endif + }; + + protected: + + DynamicMMap ⤅ + pkgCache Cache; + OpProgress *Progress; + + std::string RlsFileName; + pkgCache::ReleaseFile *CurrentRlsFile; + std::string PkgFileName; + pkgCache::PackageFile *CurrentFile; + +#ifdef APT_PKG_EXPOSE_STRING_VIEW + bool NewGroup(pkgCache::GrpIterator &Grp, APT::StringView Name); + bool NewPackage(pkgCache::PkgIterator &Pkg, APT::StringView Name, APT::StringView Arch); + map_pointer_t NewVersion(pkgCache::VerIterator &Ver, APT::StringView const &VerStr, + map_pointer_t const ParentPkg, unsigned short const Hash, + map_pointer_t const Next); + map_pointer_t NewDescription(pkgCache::DescIterator &Desc,const std::string &Lang, APT::StringView md5sum,map_stringitem_t const idxmd5str); +#endif + bool NewFileVer(pkgCache::VerIterator &Ver,ListParser &List); + bool NewFileDesc(pkgCache::DescIterator &Desc,ListParser &List); + bool NewDepends(pkgCache::PkgIterator &Pkg, pkgCache::VerIterator &Ver, + map_pointer_t const Version, uint8_t const Op, + uint8_t const Type, map_pointer_t* &OldDepLast); + bool NewProvides(pkgCache::VerIterator &Ver, pkgCache::PkgIterator &Pkg, + map_stringitem_t const ProvidesVersion, uint8_t const Flags); + + public: + + enum StringType { MIXED, PKGNAME, VERSIONNUMBER, SECTION }; + map_stringitem_t StoreString(StringType const type, const char * S, unsigned int const Size); + +#ifdef APT_PKG_EXPOSE_STRING_VIEW + inline map_stringitem_t StoreString(enum StringType const type, APT::StringView S) {return StoreString(type, S.data(),S.length());}; +#endif + + void DropProgress() {Progress = 0;}; + bool SelectFile(const std::string &File,pkgIndexFile const &Index, std::string const &Architecture, std::string const &Component, unsigned long Flags = 0); + bool SelectReleaseFile(const std::string &File, const std::string &Site, unsigned long Flags = 0); + bool MergeList(ListParser &List,pkgCache::VerIterator *Ver = 0); + inline pkgCache &GetCache() {return Cache;}; + inline pkgCache::PkgFileIterator GetCurFile() + {return pkgCache::PkgFileIterator(Cache,CurrentFile);}; + inline pkgCache::RlsFileIterator GetCurRlsFile() + {return pkgCache::RlsFileIterator(Cache,CurrentRlsFile);}; + + APT_PUBLIC static bool MakeStatusCache(pkgSourceList &List,OpProgress *Progress, + MMap **OutMap = 0,bool AllowMem = false); + APT_HIDDEN static bool MakeStatusCache(pkgSourceList &List,OpProgress *Progress, + MMap **OutMap,pkgCache **OutCache, bool AllowMem = false); + APT_PUBLIC static bool MakeOnlyStatusCache(OpProgress *Progress,DynamicMMap **OutMap); + + void ReMap(void const * const oldMap, void const * const newMap, size_t oldSize); + bool Start(); + + pkgCacheGenerator(DynamicMMap *Map,OpProgress *Progress); + virtual ~pkgCacheGenerator(); + + private: + void * const d; + APT_HIDDEN bool MergeListGroup(ListParser &List, std::string const &GrpName); + APT_HIDDEN bool MergeListPackage(ListParser &List, pkgCache::PkgIterator &Pkg); +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN bool MergeListVersion(ListParser &List, pkgCache::PkgIterator &Pkg, + APT::StringView const &Version, pkgCache::VerIterator* &OutVer); +#endif + + APT_HIDDEN bool AddImplicitDepends(pkgCache::GrpIterator &G, pkgCache::PkgIterator &P, + pkgCache::VerIterator &V); + APT_HIDDEN bool AddImplicitDepends(pkgCache::VerIterator &V, pkgCache::PkgIterator &D); + +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN bool AddNewDescription(ListParser &List, pkgCache::VerIterator &Ver, + std::string const &lang, APT::StringView CurMd5, map_stringitem_t &md5idx); +#endif +}; + /*}}}*/ +// This is the abstract package list parser class. /*{{{*/ +class APT_HIDDEN pkgCacheListParser +{ + pkgCacheGenerator *Owner; + friend class pkgCacheGenerator; + + // Some cache items + pkgCache::VerIterator OldDepVer; + map_pointer_t *OldDepLast; + + void * const d; + + protected: + + inline map_stringitem_t StoreString(pkgCacheGenerator::StringType const type, const char *S,unsigned int Size) {return Owner->StoreString(type, S, Size);}; +#ifdef APT_PKG_EXPOSE_STRING_VIEW + inline map_stringitem_t StoreString(pkgCacheGenerator::StringType const type, APT::StringView S) {return Owner->StoreString(type, S);}; + inline map_stringitem_t WriteString(APT::StringView S) {return Owner->WriteStringInMap(S.data(), S.size());}; +#endif + + inline map_stringitem_t WriteString(const char *S,unsigned int Size) {return Owner->WriteStringInMap(S,Size);}; +#ifdef APT_PKG_EXPOSE_STRING_VIEW + bool NewDepends(pkgCache::VerIterator &Ver,APT::StringView Package, APT::StringView Arch, + APT::StringView Version,uint8_t const Op, + uint8_t const Type); + bool NewProvides(pkgCache::VerIterator &Ver,APT::StringView PkgName, + APT::StringView PkgArch, APT::StringView Version, + uint8_t const Flags); + bool NewProvidesAllArch(pkgCache::VerIterator &Ver, APT::StringView Package, + APT::StringView Version, uint8_t const Flags); +#endif + public: + + // These all operate against the current section + virtual std::string Package() = 0; + virtual bool ArchitectureAll() = 0; +#ifdef APT_PKG_EXPOSE_STRING_VIEW + virtual APT::StringView Architecture() = 0; + virtual APT::StringView Version() = 0; +#endif + virtual bool NewVersion(pkgCache::VerIterator &Ver) = 0; + virtual std::vector<std::string> AvailableDescriptionLanguages() = 0; +#ifdef APT_PKG_EXPOSE_STRING_VIEW + virtual APT::StringView Description_md5() = 0; +#endif + virtual unsigned short VersionHash() = 0; + /** compare currently parsed version with given version + * + * \param Hash of the currently parsed version + * \param Ver to compare with + */ + virtual bool SameVersion(unsigned short const Hash, pkgCache::VerIterator const &Ver); + virtual bool UsePackage(pkgCache::PkgIterator &Pkg, + pkgCache::VerIterator &Ver) = 0; + virtual map_filesize_t Offset() = 0; + virtual map_filesize_t Size() = 0; + + virtual bool Step() = 0; + + virtual bool CollectFileProvides(pkgCache &/*Cache*/, + pkgCache::VerIterator &/*Ver*/) {return true;}; + + pkgCacheListParser(); + virtual ~pkgCacheListParser(); +}; + /*}}}*/ + +APT_DEPRECATED_MSG("Use pkgCacheGenerator::MakeStatusCache instead") bool pkgMakeStatusCache(pkgSourceList &List,OpProgress &Progress, + MMap **OutMap = 0,bool AllowMem = false); +APT_DEPRECATED_MSG("Use pkgCacheGenerator::MakeOnlyStatusCache instead") bool pkgMakeOnlyStatusCache(OpProgress &Progress,DynamicMMap **OutMap); + +#endif diff --git a/apt-pkg/pkgrecords.cc b/apt-pkg/pkgrecords.cc new file mode 100644 index 0000000..77aa8ce --- /dev/null +++ b/apt-pkg/pkgrecords.cc @@ -0,0 +1,79 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Package Records - Allows access to complete package description records + directly from the file. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/error.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/pkgrecords.h> + +#include <vector> +#include <stddef.h> + +#include <apti18n.h> + /*}}}*/ + +// Records::pkgRecords - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* This will create the necessary structures to access the status files */ +pkgRecords::pkgRecords(pkgCache &aCache) : d(NULL), Cache(aCache), + Files(Cache.HeaderP->PackageFileCount) +{ + for (pkgCache::PkgFileIterator I = Cache.FileBegin(); + I.end() == false; ++I) + { + const pkgIndexFile::Type *Type = pkgIndexFile::Type::GetType(I.IndexType()); + if (Type == 0) + { + _error->Error(_("Index file type '%s' is not supported"),I.IndexType()); + return; + } + + Files[I->ID] = Type->CreatePkgParser(I); + if (Files[I->ID] == 0) + return; + } +} + /*}}}*/ +// Records::~pkgRecords - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgRecords::~pkgRecords() +{ + for ( std::vector<Parser*>::iterator it = Files.begin(); + it != Files.end(); + ++it) + { + delete *it; + } +} + /*}}}*/ +// Records::Lookup - Get a parser for the package version file /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgRecords::Parser &pkgRecords::Lookup(pkgCache::VerFileIterator const &Ver) +{ + Files[Ver.File()->ID]->Jump(Ver); + return *Files[Ver.File()->ID]; +} + /*}}}*/ +// Records::Lookup - Get a parser for the package description file /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgRecords::Parser &pkgRecords::Lookup(pkgCache::DescFileIterator const &Desc) +{ + Files[Desc.File()->ID]->Jump(Desc); + return *Files[Desc.File()->ID]; +} + /*}}}*/ + +pkgRecords::Parser::Parser() : d(NULL) {} +pkgRecords::Parser::~Parser() {} diff --git a/apt-pkg/pkgrecords.h b/apt-pkg/pkgrecords.h new file mode 100644 index 0000000..6d1342d --- /dev/null +++ b/apt-pkg/pkgrecords.h @@ -0,0 +1,115 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Package Records - Allows access to complete package description records + directly from the file. + + The package record system abstracts the actual parsing of the + package files. This is different than the generators parser in that + it is used to access information not generate information. No + information touched by the generator should be parable from here as + it can always be retrieved directly from the cache. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_PKGRECORDS_H +#define PKGLIB_PKGRECORDS_H + +#include <apt-pkg/hashes.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#include <string> +#include <vector> + +class pkgRecords /*{{{*/ +{ + public: + class Parser; + + private: + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + pkgCache &Cache; + std::vector<Parser *>Files; + + public: + // Lookup function + Parser &Lookup(pkgCache::VerFileIterator const &Ver); + Parser &Lookup(pkgCache::DescFileIterator const &Desc); + + // Construct destruct + explicit pkgRecords(pkgCache &Cache); + virtual ~pkgRecords(); +}; + /*}}}*/ +class pkgRecords::Parser /*{{{*/ +{ + protected: + + virtual bool Jump(pkgCache::VerFileIterator const &Ver) = 0; + virtual bool Jump(pkgCache::DescFileIterator const &Desc) = 0; + + public: + friend class pkgRecords; + + // These refer to the archive file for the Version + virtual std::string FileName() {return std::string();}; + virtual std::string SourcePkg() {return std::string();}; + virtual std::string SourceVer() {return std::string();}; + + /** return all known hashes in this record. + * + * For authentication proposes packages come with hashsums which + * this method is supposed to parse and return so that clients can + * choose the hash to be used. + */ + virtual HashStringList Hashes() const { return HashStringList(); }; + APT_DEPRECATED_MSG("Use .Hashes instead of a hardcoded hash algorithm") std::string MD5Hash() const { return GetHashFromHashes("MD5Sum"); }; + APT_DEPRECATED_MSG("Use .Hashes instead of a hardcoded hash algorithm") std::string SHA1Hash() const { return GetHashFromHashes("SHA1"); }; + APT_DEPRECATED_MSG("Use .Hashes instead of a hardcoded hash algorithm") std::string SHA256Hash() const { return GetHashFromHashes("SHA256"); }; + APT_DEPRECATED_MSG("Use .Hashes instead of a hardcoded hash algorithm") std::string SHA512Hash() const { return GetHashFromHashes("SHA512"); }; + + // These are some general stats about the package + virtual std::string Maintainer() {return std::string();}; + /** return short description in language from record. + * + * @see #LongDesc + */ + virtual std::string ShortDesc(std::string const &/*lang*/) {return std::string();}; + /** return long description in language from record. + * + * If \b lang is empty the "best" available language will be + * returned as determined by the APT::Languages configuration. + * If a (requested) language can't be found in this record an empty + * string will be returned. + */ + virtual std::string LongDesc(std::string const &/*lang*/) {return std::string();}; + std::string ShortDesc() {return ShortDesc("");}; + std::string LongDesc() {return LongDesc("");}; + + virtual std::string Name() {return std::string();}; + virtual std::string Homepage() {return std::string();} + + // An arbitrary custom field + virtual std::string RecordField(const char * /*fieldName*/) { return std::string();}; + + // The record in binary form + virtual void GetRec(const char *&Start,const char *&Stop) {Start = Stop = 0;}; + + Parser(); + virtual ~Parser(); + + private: + void * const d; + APT_HIDDEN std::string GetHashFromHashes(char const * const type) const + { + HashStringList const hashes = Hashes(); + HashString const * const hs = hashes.find(type); + return hs != NULL ? hs->HashValue() : ""; + }; +}; + /*}}}*/ +#endif diff --git a/apt-pkg/pkgsystem.cc b/apt-pkg/pkgsystem.cc new file mode 100644 index 0000000..eaa3f5a --- /dev/null +++ b/apt-pkg/pkgsystem.cc @@ -0,0 +1,113 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + System - Abstraction for running on different systems. + + Basic general structure.. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/debsystem.h> +#include <apt-pkg/error.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgsystem.h> + +#include <cassert> +#include <cstring> +#include <map> + /*}}}*/ + +pkgSystem *_system = 0; +static pkgSystem *SysList[10]; +pkgSystem **pkgSystem::GlobalList = SysList; +unsigned long pkgSystem::GlobalListLen = 0; + +class APT_HIDDEN pkgSystemPrivate /*{{{*/ +{ +public: + typedef decltype(pkgCache::Version::ID) idtype; + std::map<idtype,idtype> idmap; + pkgSystemPrivate() {} +}; + /*}}}*/ +// System::pkgSystem - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* Add it to the global list.. */ +pkgSystem::pkgSystem(char const * const label, pkgVersioningSystem * const vs) : + Label(label), VS(vs), d(new pkgSystemPrivate()) +{ + assert(GlobalListLen < sizeof(SysList)/sizeof(*SysList)); + SysList[GlobalListLen] = this; + ++GlobalListLen; +} + /*}}}*/ +// System::GetSystem - Get the named system /*{{{*/ +// --------------------------------------------------------------------- +/* */ +APT_PURE pkgSystem *pkgSystem::GetSystem(const char *Label) +{ + for (unsigned I = 0; I != GlobalListLen; I++) + if (strcmp(SysList[I]->Label,Label) == 0) + return SysList[I]; + return 0; +} + /*}}}*/ +bool pkgSystem::MultiArchSupported() const /*{{{*/ +{ + debSystem const * const deb = dynamic_cast<debSystem const *>(this); + if (deb != NULL) + return deb->SupportsMultiArch(); + return true; +} + /*}}}*/ +std::vector<std::string> pkgSystem::ArchitecturesSupported() const /*{{{*/ +{ + debSystem const * const deb = dynamic_cast<debSystem const *>(this); + if (deb != NULL) + return deb->SupportedArchitectures(); + return {}; +} + /*}}}*/ +// pkgSystem::Set/GetVersionMapping - for internal/external communication/*{{{*/ +void pkgSystem::SetVersionMapping(map_id_t const in, map_id_t const out) +{ + if (in == out) + return; + d->idmap.emplace(in, out); +} +map_id_t pkgSystem::GetVersionMapping(map_id_t const in) const +{ + auto const o = d->idmap.find(in); + return (o == d->idmap.end()) ? in : o->second; +} + /*}}}*/ + +bool pkgSystem::LockInner() /*{{{*/ +{ + debSystem * const deb = dynamic_cast<debSystem *>(this); + if (deb != NULL) + return deb->LockInner(); + return _error->Error("LockInner is not implemented"); +} + /*}}}*/ +bool pkgSystem::UnLockInner(bool NoErrors) /*{{{*/ +{ + debSystem * const deb = dynamic_cast<debSystem *>(this); + if (deb != NULL) + return deb->UnLockInner(NoErrors); + return _error->Error("UnLockInner is not implemented"); +} + /*}}}*/ +bool pkgSystem::IsLocked() /*{{{*/ +{ + debSystem * const deb = dynamic_cast<debSystem *>(this); + if (deb != NULL) + return deb->IsLocked(); + return true; +} + /*}}}*/ +pkgSystem::~pkgSystem() {} diff --git a/apt-pkg/pkgsystem.h b/apt-pkg/pkgsystem.h new file mode 100644 index 0000000..b420705 --- /dev/null +++ b/apt-pkg/pkgsystem.h @@ -0,0 +1,141 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + System - Abstraction for running on different systems. + + Instances of this class can be thought of as factories or meta-classes + for a variety of more specialized classes. Together this class and + it's specialized offspring completely define the environment and how + to access resources for a specific system. There are several sub + areas that are all orthogonal - each system has a unique combination of + these sub areas: + - Versioning. Different systems have different ideas on versions. + Within a system all sub classes must follow the same versioning + rules. + - Local tool locking to prevent multiple tools from accessing the + same database. + - Candidate Version selection policy - this is probably almost always + managed using a standard APT class + - Actual Package installation + * Indication of what kind of binary formats are supported + - Selection of local 'status' indexes that make up the pkgCache. + + It is important to note that the handling of index files is not a + function of the system. Index files are handled through a separate + abstraction - the only requirement is that the index files have the + same idea of versioning as the target system. + + Upon startup each supported system instantiates an instance of the + pkgSystem class (using a global constructor) which will make itself + available to the main APT init routine. That routine will select the + proper system and make it the global default. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_PKGSYSTEM_H +#define PKGLIB_PKGSYSTEM_H + +#include <apt-pkg/pkgcache.h> + +#include <vector> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/depcache.h> +#endif + +class pkgDepCache; +class pkgPackageManager; +class pkgVersioningSystem; +class Configuration; +class pkgIndexFile; + +class pkgSystemPrivate; +class pkgSystem +{ + public: + + // Global list of supported systems + static pkgSystem **GlobalList; + static unsigned long GlobalListLen; + static pkgSystem *GetSystem(const char *Label); + + const char * const Label; + pkgVersioningSystem * const VS; + + /* Prevent other programs from touching shared data not covered by + other locks (cache or state locks) */ + virtual bool Lock() = 0; + virtual bool UnLock(bool NoErrors = false) = 0; + + /* Various helper classes to interface with specific bits of this + environment */ + virtual pkgPackageManager *CreatePM(pkgDepCache *Cache) const = 0; + + /* Load environment specific configuration and perform any other setup + necessary */ + virtual bool Initialize(Configuration &/*Cnf*/) {return true;}; + + /* Type is some kind of Globally Unique way of differentiating + archive file types.. */ + virtual bool ArchiveSupported(const char *Type) = 0; + + // Return a list of system index files.. + virtual bool AddStatusFiles(std::vector<pkgIndexFile *> &List) = 0; + + virtual bool FindIndex(pkgCache::PkgFileIterator File, + pkgIndexFile *&Found) const = 0; + + /* Evauluate how 'right' we are for this system based on the filesystem + etc.. */ + virtual signed Score(Configuration const &/*Cnf*/) { + return 0; + }; + + //FIXME: these methods should be virtual + /** does this system has support for MultiArch? + * + * Systems supporting only single arch (not systems which are single arch) + * are considered legacy systems and support for it will likely degrade over + * time. + * + * The default implementation returns always \b true. + * + * @return \b true if the system supports MultiArch, \b false if not. + */ + bool MultiArchSupported() const; + /** architectures supported by this system + * + * A MultiArch capable system might be configured to use + * this capability. + * + * @return a list of all architectures (native + foreign) configured + * for on this system (aka: which can be installed without force) + */ + std::vector<std::string> ArchitecturesSupported() const; + + APT_HIDDEN void SetVersionMapping(map_id_t const in, map_id_t const out); + APT_HIDDEN map_id_t GetVersionMapping(map_id_t const in) const; + + pkgSystem(char const * const Label, pkgVersioningSystem * const VS); + virtual ~pkgSystem(); + + + /* companions to Lock()/UnLock + * + * These functions can be called prior to calling dpkg to release an inner + * lock without releasing the overall outer lock, so that dpkg can run + * correctly but no other APT instance can acquire the system lock. + */ + bool LockInner(); + bool UnLockInner(bool NoErrors = false); + /// checks if the system is currently locked + bool IsLocked(); + private: + pkgSystemPrivate * const d; +}; + +// The environment we are operating in. +extern pkgSystem *_system; + +#endif diff --git a/apt-pkg/policy.cc b/apt-pkg/policy.cc new file mode 100644 index 0000000..7986aa5 --- /dev/null +++ b/apt-pkg/policy.cc @@ -0,0 +1,442 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Package Version Policy implementation + + This is just a really simple wrapper around pkgVersionMatch with + some added goodies to manage the list of things.. + + See man apt_preferences for what value means what. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/cachefilter.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/netrc.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/policy.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile.h> +#include <apt-pkg/version.h> +#include <apt-pkg/versionmatch.h> + +#include <iostream> +#include <sstream> +#include <string> +#include <vector> +#include <ctype.h> +#include <stddef.h> +#include <string.h> + +#include <apti18n.h> + /*}}}*/ + +using namespace std; + +constexpr short NEVER_PIN = std::numeric_limits<short>::min(); + +// Policy::Init - Startup and bind to a cache /*{{{*/ +// --------------------------------------------------------------------- +/* Set the defaults for operation. The default mode with no loaded policy + file matches the V0 policy engine. */ +pkgPolicy::pkgPolicy(pkgCache *Owner) : Pins(nullptr), VerPins(nullptr), + PFPriority(nullptr), Cache(Owner), d(NULL) +{ + if (Owner == 0) + return; + PFPriority = new signed short[Owner->Head().PackageFileCount]; + auto PackageCount = Owner->Head().PackageCount; + Pins = new Pin[PackageCount]; + VerPins = new Pin[Owner->Head().VersionCount]; + + for (decltype(PackageCount) I = 0; I != PackageCount; ++I) + Pins[I].Type = pkgVersionMatch::None; + auto VersionCount = Owner->Head().VersionCount; + for (decltype(VersionCount) I = 0; I != VersionCount; ++I) + VerPins[I].Type = pkgVersionMatch::None; + + // The config file has a master override. + string DefRel = _config->Find("APT::Default-Release"); + if (DefRel.empty() == false) + { + bool found = false; + // FIXME: make ExpressionMatches static to use it here easily + pkgVersionMatch vm("", pkgVersionMatch::None); + for (pkgCache::PkgFileIterator F = Cache->FileBegin(); F != Cache->FileEnd(); ++F) + { + if (vm.ExpressionMatches(DefRel, F.Archive()) || + vm.ExpressionMatches(DefRel, F.Codename()) || + vm.ExpressionMatches(DefRel, F.Version()) || + (DefRel.length() > 2 && DefRel[1] == '=')) + found = true; + } + if (found == false) + _error->Error(_("The value '%s' is invalid for APT::Default-Release as such a release is not available in the sources"), DefRel.c_str()); + else + CreatePin(pkgVersionMatch::Release,"",DefRel,990); + } + InitDefaults(); +} + /*}}}*/ +// Policy::InitDefaults - Compute the default selections /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgPolicy::InitDefaults() +{ + std::vector<std::unique_ptr<FileFd>> authconfs; + // Initialize the priorities based on the status of the package file + for (pkgCache::PkgFileIterator I = Cache->FileBegin(); I != Cache->FileEnd(); ++I) + { + PFPriority[I->ID] = 500; + if (I.Flagged(pkgCache::Flag::NotSource)) + PFPriority[I->ID] = 100; + else if (I.Flagged(pkgCache::Flag::ButAutomaticUpgrades)) + PFPriority[I->ID] = 100; + else if (I.Flagged(pkgCache::Flag::NotAutomatic)) + PFPriority[I->ID] = 1; + if (I.Flagged(pkgCache::Flag::PackagesRequireAuthorization) && !IsAuthorized(I, authconfs)) + PFPriority[I->ID] = NEVER_PIN; + } + + // Apply the defaults.. + std::unique_ptr<bool[]> Fixed(new bool[Cache->HeaderP->PackageFileCount]); + memset(Fixed.get(),0,sizeof(Fixed[0])*Cache->HeaderP->PackageFileCount); + StatusOverride = false; + for (vector<Pin>::const_iterator I = Defaults.begin(); I != Defaults.end(); ++I) + { + pkgVersionMatch Match(I->Data,I->Type); + for (pkgCache::PkgFileIterator F = Cache->FileBegin(); F != Cache->FileEnd(); ++F) + { + if ((Fixed[F->ID] == false || I->Priority == NEVER_PIN) && PFPriority[F->ID] != NEVER_PIN && Match.FileMatch(F) == true) + { + PFPriority[F->ID] = I->Priority; + + if (PFPriority[F->ID] >= 1000) + StatusOverride = true; + + Fixed[F->ID] = true; + } + } + } + + if (_config->FindB("Debug::pkgPolicy",false) == true) + for (pkgCache::PkgFileIterator F = Cache->FileBegin(); F != Cache->FileEnd(); ++F) + std::clog << "Prio of " << F.FileName() << ' ' << PFPriority[F->ID] << std::endl; + + return true; +} + /*}}}*/ +// Policy::GetCandidateVer - Get the candidate install version /*{{{*/ +// --------------------------------------------------------------------- +/* Evaluate the package pins and the default list to deteremine what the + best package is. */ +pkgCache::VerIterator pkgPolicy::GetCandidateVer(pkgCache::PkgIterator const &Pkg) +{ + pkgCache::VerIterator cand; + pkgCache::VerIterator cur = Pkg.CurrentVer(); + int candPriority = -1; + pkgVersioningSystem *vs = Cache->VS; + + for (pkgCache::VerIterator ver = Pkg.VersionList(); ver.end() == false; ++ver) { + int priority = GetPriority(ver, true); + + if (priority == 0 || priority <= candPriority) + continue; + + // TODO: Maybe optimize to not compare versions + if (!cur.end() && priority < 1000 + && (vs->CmpVersion(ver.VerStr(), cur.VerStr()) < 0)) + continue; + + candPriority = priority; + cand = ver; + } + + return cand; +} + /*}}}*/ +// Policy::CreatePin - Create an entry in the pin table.. /*{{{*/ +// --------------------------------------------------------------------- +/* For performance we have 3 tables, the default table, the main cache + table (hashed to the cache). A blank package name indicates the pin + belongs to the default table. Order of insertion matters here, the + earlier defaults override later ones. */ +void pkgPolicy::CreatePin(pkgVersionMatch::MatchType Type,string Name, + string Data,signed short Priority) +{ + if (Name.empty() == true) + { + Pin *P = &*Defaults.insert(Defaults.end(),Pin()); + P->Type = Type; + P->Priority = Priority; + P->Data = Data; + return; + } + + size_t found = Name.rfind(':'); + string Arch; + if (found != string::npos) { + Arch = Name.substr(found+1); + Name.erase(found); + } + + // Allow pinning by wildcards - beware of package names looking like wildcards! + // TODO: Maybe we should always prefer specific pins over non-specific ones. + if ((Name[0] == '/' && Name[Name.length() - 1] == '/') || Name.find_first_of("*[?") != string::npos) + { + pkgVersionMatch match(Data, Type); + for (pkgCache::GrpIterator G = Cache->GrpBegin(); G.end() != true; ++G) + if (Name != G.Name() && match.ExpressionMatches(Name, G.Name())) + { + if (Arch.empty() == false) + CreatePin(Type, string(G.Name()).append(":").append(Arch), Data, Priority); + else + CreatePin(Type, G.Name(), Data, Priority); + } + return; + } + + // find the package (group) this pin applies to + pkgCache::GrpIterator Grp = Cache->FindGrp(Name); + bool matched = false; + if (Grp.end() == false) + { + std::string MatchingArch; + if (Arch.empty() == true) + MatchingArch = Cache->NativeArch(); + else + MatchingArch = Arch; + APT::CacheFilter::PackageArchitectureMatchesSpecification pams(MatchingArch); + for (pkgCache::PkgIterator Pkg = Grp.PackageList(); Pkg.end() != true; Pkg = Grp.NextPkg(Pkg)) + { + if (pams(Pkg.Arch()) == false) + continue; + Pin *P = Pins + Pkg->ID; + // the first specific stanza for a package is the ruler, + // all others need to be ignored + if (P->Type != pkgVersionMatch::None) + P = &*Unmatched.insert(Unmatched.end(),PkgPin(Pkg.FullName())); + P->Type = Type; + P->Priority = Priority; + P->Data = Data; + matched = true; + + // Find matching version(s) and copy the pin into it + pkgVersionMatch Match(P->Data,P->Type); + for (pkgCache::VerIterator Ver = Pkg.VersionList(); Ver.end() != true; ++Ver) + { + if (Match.VersionMatches(Ver)) { + Pin *VP = VerPins + Ver->ID; + if (VP->Type == pkgVersionMatch::None) + *VP = *P; + } + } + } + } + + if (matched == false) + { + PkgPin *P = &*Unmatched.insert(Unmatched.end(),PkgPin(Name)); + if (Arch.empty() == false) + P->Pkg.append(":").append(Arch); + P->Type = Type; + P->Priority = Priority; + P->Data = Data; + return; + } +} + /*}}}*/ +// Policy::GetMatch - Get the matching version for a package pin /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgCache::VerIterator pkgPolicy::GetMatch(pkgCache::PkgIterator const &Pkg) +{ + const Pin &PPkg = Pins[Pkg->ID]; + if (PPkg.Type == pkgVersionMatch::None) + return pkgCache::VerIterator(*Pkg.Cache()); + + pkgVersionMatch Match(PPkg.Data,PPkg.Type); + return Match.Find(Pkg); +} + /*}}}*/ +// Policy::GetPriority - Get the priority of the package pin /*{{{*/ +// --------------------------------------------------------------------- +/* */ +APT_PURE signed short pkgPolicy::GetPriority(pkgCache::PkgIterator const &Pkg) +{ + if (Pins[Pkg->ID].Type != pkgVersionMatch::None) + return Pins[Pkg->ID].Priority; + return 0; +} +APT_PURE signed short pkgPolicy::GetPriority(pkgCache::VerIterator const &Ver, bool ConsiderFiles) +{ + if (VerPins[Ver->ID].Type != pkgVersionMatch::None) + { + // If all sources are never pins, the never pin wins. + if (VerPins[Ver->ID].Priority == NEVER_PIN) + return NEVER_PIN; + for (pkgCache::VerFileIterator file = Ver.FileList(); file.end() == false; file++) + if (GetPriority(file.File()) != NEVER_PIN) + return VerPins[Ver->ID].Priority; + } + if (!ConsiderFiles) + return 0; + + // priorities are short ints, but we want to pick a value outside the valid range here + auto priority = std::numeric_limits<signed int>::min(); + for (pkgCache::VerFileIterator file = Ver.FileList(); file.end() == false; file++) + { + /* 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 (file.File().Flagged(pkgCache::Flag::NotSource) && Ver.ParentPkg().CurrentVer() != Ver) + priority = std::max(priority, static_cast<decltype(priority)>(-1)); + else + priority = std::max(priority, static_cast<decltype(priority)>(GetPriority(file.File()))); + } + + return priority == std::numeric_limits<decltype(priority)>::min() ? 0 : priority; +} +APT_PURE signed short pkgPolicy::GetPriority(pkgCache::PkgFileIterator const &File) +{ + return PFPriority[File->ID]; +} + /*}}}*/ +// ReadPinDir - Load the pin files from this dir into a Policy /*{{{*/ +// --------------------------------------------------------------------- +/* This will load each pin file in the given dir into a Policy. If the + given dir is empty the dir set in Dir::Etc::PreferencesParts is used. + Note also that this method will issue a warning if the dir does not + exists but it will return true in this case! */ +bool ReadPinDir(pkgPolicy &Plcy,string Dir) +{ + if (Dir.empty() == true) + Dir = _config->FindDir("Dir::Etc::PreferencesParts", "/dev/null"); + + if (DirectoryExists(Dir) == false) + { + if (APT::String::Endswith(Dir, "/dev/null") == false) + _error->WarningE("DirectoryExists",_("Unable to read %s"),Dir.c_str()); + return true; + } + + _error->PushToStack(); + vector<string> const List = GetListOfFilesInDir(Dir, "pref", true, true); + bool const PendingErrors = _error->PendingError(); + _error->MergeWithStack(); + if (PendingErrors) + return false; + + // Read the files + bool good = true; + for (vector<string>::const_iterator I = List.begin(); I != List.end(); ++I) + good = ReadPinFile(Plcy, *I) && good; + return good; +} + /*}}}*/ +// ReadPinFile - Load the pin file into a Policy /*{{{*/ +// --------------------------------------------------------------------- +/* I'd like to see the preferences file store more than just pin information + but right now that is the only stuff I have to store. Later there will + have to be some kind of combined super parser to get the data into all + the right classes.. */ +bool ReadPinFile(pkgPolicy &Plcy,string File) +{ + if (File.empty() == true) + File = _config->FindFile("Dir::Etc::Preferences"); + + if (RealFileExists(File) == false) + return true; + + FileFd Fd; + if (OpenConfigurationFileFd(File, Fd) == false) + return false; + + pkgTagFile TF(&Fd, pkgTagFile::SUPPORT_COMMENTS); + if (Fd.IsOpen() == false || Fd.Failed()) + return false; + + pkgTagSection Tags; + while (TF.Step(Tags) == true) + { + // can happen when there are only comments in a record + if (Tags.Count() == 0) + continue; + + string Name = Tags.FindS("Package"); + if (Name.empty() == true) + return _error->Error(_("Invalid record in the preferences file %s, no Package header"), File.c_str()); + if (Name == "*") + Name = string(); + + const char *Start; + const char *End; + if (Tags.Find("Pin",Start,End) == false) + continue; + + const char *Word = Start; + for (; Word != End && isspace(*Word) == 0; Word++); + + // Parse the type.. + pkgVersionMatch::MatchType Type; + if (stringcasecmp(Start,Word,"version") == 0 && Name.empty() == false) + Type = pkgVersionMatch::Version; + else if (stringcasecmp(Start,Word,"release") == 0) + Type = pkgVersionMatch::Release; + else if (stringcasecmp(Start,Word,"origin") == 0) + Type = pkgVersionMatch::Origin; + else + { + _error->Warning(_("Did not understand pin type %s"),string(Start,Word).c_str()); + continue; + } + for (; Word != End && isspace(*Word) != 0; Word++); + + _error->PushToStack(); + std::string sPriority = Tags.FindS("Pin-Priority"); + int priority = sPriority == "never" ? NEVER_PIN : Tags.FindI("Pin-Priority", 0); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + + if (sPriority == "never" && not Name.empty()) + return _error->Error(_("%s: The special 'Pin-Priority: %s' can only be used for 'Package: *' records"), File.c_str(), "never"); + + // Silently clamp the never pin to never pin + 1 + if (priority == NEVER_PIN && sPriority != "never") + priority = NEVER_PIN + 1; + if (priority < std::numeric_limits<short>::min() || + priority > std::numeric_limits<short>::max() || + newError) { + return _error->Error(_("%s: Value %s is outside the range of valid pin priorities (%d to %d)"), + File.c_str(), Tags.FindS("Pin-Priority").c_str(), + std::numeric_limits<short>::min(), + std::numeric_limits<short>::max()); + } + if (priority == 0) + { + return _error->Error(_("No priority (or zero) specified for pin")); + } + + istringstream s(Name); + string pkg; + while(!s.eof()) + { + s >> pkg; + Plcy.CreatePin(Type, pkg, string(Word,End),priority); + }; + } + + Plcy.InitDefaults(); + return true; +} + /*}}}*/ + +pkgPolicy::~pkgPolicy() {delete [] PFPriority; delete [] Pins; delete [] VerPins; } diff --git a/apt-pkg/policy.h b/apt-pkg/policy.h new file mode 100644 index 0000000..bcd1daf --- /dev/null +++ b/apt-pkg/policy.h @@ -0,0 +1,96 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Package Version Policy implementation + + This implements the more advanced 'Version 4' APT policy engine. The + standard 'Version 0' engine is included inside the DepCache which is + it's historical location. + + The V4 engine allows the user to completely control all aspects of + version selection. There are three primary means to choose a version + * Selection by version match + * Selection by Release file match + * Selection by origin server + + Each package may be 'pinned' with a single criteria, which will ultimately + result in the selection of a single version, or no version, for each + package. + + Furthermore, the default selection can be influenced by specifying + the ordering of package files. The order is derived by reading the + package file preferences and assigning a priority to each package + file. + + A special flag may be set to indicate if no version should be returned + if no matching versions are found, otherwise the default matching + rules are used to locate a hit. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_POLICY_H +#define PKGLIB_POLICY_H + +#include <apt-pkg/depcache.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/versionmatch.h> + +#include <string> +#include <vector> + +#ifndef APT_8_CLEANER_HEADERS +using std::vector; +#endif + +class pkgPolicy : public pkgDepCache::Policy +{ + protected: + + struct Pin + { + pkgVersionMatch::MatchType Type; + std::string Data; + signed short Priority; + Pin() : Type(pkgVersionMatch::None), Priority(0) {}; + }; + + struct PkgPin : Pin + { + std::string Pkg; + explicit PkgPin(std::string const &Pkg) : Pin(), Pkg(Pkg) {}; + }; + + Pin *Pins; + Pin *VerPins; + signed short *PFPriority; + std::vector<Pin> Defaults; + std::vector<PkgPin> Unmatched; + pkgCache *Cache; + bool StatusOverride; + + public: + + // Things for manipulating pins + void CreatePin(pkgVersionMatch::MatchType Type,std::string Pkg, + std::string Data,signed short Priority); + pkgCache::VerIterator GetMatch(pkgCache::PkgIterator const &Pkg); + + // Things for the cache interface. + virtual pkgCache::VerIterator GetCandidateVer(pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual signed short GetPriority(pkgCache::PkgIterator const &Pkg) APT_OVERRIDE; + virtual signed short GetPriority(pkgCache::VerIterator const &Ver, bool ConsiderFiles = true) APT_OVERRIDE; + virtual signed short GetPriority(pkgCache::PkgFileIterator const &File) APT_OVERRIDE; + + bool InitDefaults(); + + explicit pkgPolicy(pkgCache *Owner); + virtual ~pkgPolicy(); + private: + void * const d; +}; + +bool ReadPinFile(pkgPolicy &Plcy, std::string File = ""); +bool ReadPinDir(pkgPolicy &Plcy, std::string Dir = ""); + +#endif diff --git a/apt-pkg/prettyprinters.cc b/apt-pkg/prettyprinters.cc new file mode 100644 index 0000000..87e78bb --- /dev/null +++ b/apt-pkg/prettyprinters.cc @@ -0,0 +1,123 @@ +// Description /*{{{*/ +/* ###################################################################### + + Provide pretty printers for pkgCache structs like PkgIterator + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/depcache.h> +#include <apt-pkg/prettyprinters.h> + +#include <ostream> +#include <string> + + /*}}}*/ + +std::ostream& operator<<(std::ostream& os, const APT::PrettyPkg& pp) /*{{{*/ +{ + if (pp.Pkg.end() == true) + return os << "invalid package"; + + auto state = (*pp.DepCache)[pp.Pkg]; + std::string const current = (pp.Pkg.CurVersion() == 0 ? "none" : pp.Pkg.CurVersion()); + std::string candidate = state.CandVersion; + if (candidate.empty()) + candidate = "none"; + std::string install = "none"; + if (state.InstallVer != nullptr) + install = state.InstVerIter(*pp.DepCache).VerStr(); + + os << pp.Pkg.FullName(false) << " < " << current; + if (current != install && install != "none") + os << " -> " << install; + if (install != candidate && current != candidate) + os << " | " << candidate; + os << " @"; + switch (pp.Pkg->SelectedState) + { + case pkgCache::State::Unknown: os << 'u'; break; + case pkgCache::State::Install: os << 'i'; break; + case pkgCache::State::Hold: os << 'h'; break; + case pkgCache::State::DeInstall: os << 'r'; break; + case pkgCache::State::Purge: os << 'p'; break; + default: os << 'X'; + } + switch (pp.Pkg->InstState) + { + case pkgCache::State::Ok: break; + case pkgCache::State::ReInstReq: os << 'R'; break; + case pkgCache::State::HoldInst: os << 'H'; break; + case pkgCache::State::HoldReInstReq: os << "HR"; break; + default: os << 'X'; + } + switch (pp.Pkg->CurrentState) + { + case pkgCache::State::NotInstalled: os << 'n'; break; + case pkgCache::State::ConfigFiles: os << 'c'; break; + case pkgCache::State::HalfInstalled: os << 'H'; break; + case pkgCache::State::UnPacked: os << 'U'; break; + case pkgCache::State::HalfConfigured: os << 'F'; break; + case pkgCache::State::TriggersAwaited: os << 'W'; break; + case pkgCache::State::TriggersPending: os << 'T'; break; + case pkgCache::State::Installed: os << 'i'; break; + default: os << 'X'; + } + os << ' '; + if (state.Protect()) + os << "p"; + if (state.ReInstall()) + os << "r"; + if (state.Upgradable()) + os << "u"; + if (state.Marked) + os << "m"; + if (state.Garbage) + os << "g"; + if (state.NewInstall()) + os << "N"; + else if (state.Upgrade()) + os << "U"; + else if (state.Downgrade()) + os << "D"; + else if (state.Install()) + os << "I"; + else if (state.Purge()) + os << "P"; + else if (state.Delete()) + os << "R"; + else if (state.Held()) + os << "H"; + else if (state.Keep()) + os << "K"; + if (state.NowBroken()) + os << " Nb"; + else if (state.NowPolicyBroken()) + os << " NPb"; + if (state.InstBroken()) + os << " Ib"; + else if (state.InstPolicyBroken()) + os << " IPb"; + os << " >"; + return os; +} + /*}}}*/ +std::ostream& operator<<(std::ostream& os, const APT::PrettyDep& pd) /*{{{*/ +{ + if (unlikely(pd.Dep.end() == true)) + return os << "invalid dependency"; + + pkgCache::PkgIterator P = pd.Dep.ParentPkg(); + pkgCache::PkgIterator T = pd.Dep.TargetPkg(); + + os << (P.end() ? "invalid pkg" : P.FullName(false)) << " " << pd.Dep.DepType() + << " on " << APT::PrettyPkg(pd.DepCache, T); + + if (pd.Dep->Version != 0) + os << " (" << pd.Dep.CompType() << " " << pd.Dep.TargetVer() << ")"; + + return os; +} + /*}}}*/ diff --git a/apt-pkg/prettyprinters.h b/apt-pkg/prettyprinters.h new file mode 100644 index 0000000..40f9d66 --- /dev/null +++ b/apt-pkg/prettyprinters.h @@ -0,0 +1,37 @@ +#ifndef APT_PRETTYPRINTERS_H +#define APT_PRETTYPRINTERS_H +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +class pkgDepCache; + +namespace APT { + +/** helper to format PkgIterator for easier printing in debug messages. + * + * The actual text generated is subject to change without prior notice + * and should NOT be used as part of a general user interface. + */ +struct PrettyPkg +{ + pkgDepCache * const DepCache; + pkgCache::PkgIterator const Pkg; + PrettyPkg(pkgDepCache * const depcache, pkgCache::PkgIterator const &pkg) APT_NONNULL(2) : DepCache(depcache), Pkg(pkg) {} +}; +/** helper to format DepIterator for easier printing in debug messages. + * + * The actual text generated is subject to change without prior notice + * and should NOT be used as part of a general user interface. + */ +struct PrettyDep +{ + pkgDepCache * const DepCache; + pkgCache::DepIterator const Dep; + PrettyDep(pkgDepCache * const depcache, pkgCache::DepIterator const &dep) APT_NONNULL(2) : DepCache(depcache), Dep(dep) {} +}; + +} +std::ostream& operator<<(std::ostream& os, const APT::PrettyPkg& pp); +std::ostream& operator<<(std::ostream& os, const APT::PrettyDep& pd); + +#endif diff --git a/apt-pkg/sourcelist.cc b/apt-pkg/sourcelist.cc new file mode 100644 index 0000000..a3d6931 --- /dev/null +++ b/apt-pkg/sourcelist.cc @@ -0,0 +1,643 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + List of Sources + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/debindexfile.h> +#include <apt-pkg/debsrcrecords.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/metaindex.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/sourcelist.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile.h> + +#include <algorithm> +#include <cstring> +#include <fstream> +#include <map> +#include <string> +#include <vector> +#include <ctype.h> +#include <stddef.h> +#include <time.h> + +#include <apti18n.h> + /*}}}*/ + +using namespace std; + +// Global list of Items supported +static pkgSourceList::Type *ItmList[10]; +pkgSourceList::Type **pkgSourceList::Type::GlobalList = ItmList; +unsigned long pkgSourceList::Type::GlobalListLen = 0; + +static std::vector<std::string> FindMultiValue(pkgTagSection &Tags, char const *const Field) /*{{{*/ +{ + auto values = Tags.FindS(Field); + // we ignore duplicate spaces by removing empty values + std::replace_if(values.begin(), values.end(), isspace_ascii, ' '); + auto vect = VectorizeString(values, ' '); + vect.erase(std::remove_if(vect.begin(), vect.end(), [](std::string const &s) { return s.empty(); }), vect.end()); + return vect; +} + /*}}}*/ + +// Type::Type - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* Link this to the global list of items*/ +pkgSourceList::Type::Type(char const * const pName, char const * const pLabel) : Name(pName), Label(pLabel) +{ + ItmList[GlobalListLen] = this; + ++GlobalListLen; +} +pkgSourceList::Type::~Type() {} + /*}}}*/ +// Type::GetType - Get a specific meta for a given type /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgSourceList::Type *pkgSourceList::Type::GetType(const char *Type) +{ + for (unsigned I = 0; I != GlobalListLen; ++I) + if (strcmp(GlobalList[I]->Name,Type) == 0) + return GlobalList[I]; + return 0; +} + /*}}}*/ +// Type::FixupURI - Normalize the URI and check it.. /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgSourceList::Type::FixupURI(string &URI) const +{ + if (URI.empty() == true) + return false; + + if (URI.find(':') == string::npos) + return false; + + URI = SubstVar(URI,"$(ARCH)",_config->Find("APT::Architecture")); + + // Make sure that the URI is / postfixed + if (URI[URI.size() - 1] != '/') + URI += '/'; + + return true; +} + /*}}}*/ +bool pkgSourceList::Type::ParseStanza(vector<metaIndex *> &List, /*{{{*/ + pkgTagSection &Tags, + unsigned int const i, + FileFd &Fd) +{ + map<string, string> Options; + + string Enabled = Tags.FindS("Enabled"); + if (Enabled.empty() == false && StringToBool(Enabled) == false) + return true; + + std::map<char const * const, std::pair<char const * const, bool> > mapping; +#define APT_PLUSMINUS(X, Y) \ + mapping.insert(std::make_pair(X, std::make_pair(Y, true))); \ + mapping.insert(std::make_pair(X "-Add", std::make_pair(Y "+", true))); \ + mapping.insert(std::make_pair(X "-Remove", std::make_pair(Y "-", true))) + APT_PLUSMINUS("Architectures", "arch"); + APT_PLUSMINUS("Languages", "lang"); + APT_PLUSMINUS("Targets", "target"); +#undef APT_PLUSMINUS + mapping.insert(std::make_pair("Trusted", std::make_pair("trusted", false))); + mapping.insert(std::make_pair("Check-Valid-Until", std::make_pair("check-valid-until", false))); + mapping.insert(std::make_pair("Valid-Until-Min", std::make_pair("valid-until-min", false))); + mapping.insert(std::make_pair("Valid-Until-Max", std::make_pair("valid-until-max", false))); + mapping.insert(std::make_pair("Check-Date", std::make_pair("check-date", false))); + mapping.insert(std::make_pair("Date-Max-Future", std::make_pair("date-max-future", false))); + mapping.insert(std::make_pair("Signed-By", std::make_pair("signed-by", false))); + mapping.insert(std::make_pair("PDiffs", std::make_pair("pdiffs", false))); + mapping.insert(std::make_pair("By-Hash", std::make_pair("by-hash", false))); + + for (std::map<char const * const, std::pair<char const * const, bool> >::const_iterator m = mapping.begin(); m != mapping.end(); ++m) + if (Tags.Exists(m->first)) + { + if (m->second.second) + { + auto const values = FindMultiValue(Tags, m->first); + Options[m->second.first] = APT::String::Join(values, ","); + } + else + Options[m->second.first] = Tags.FindS(m->first); + } + + { + std::string entry; + strprintf(entry, "%s:%i", Fd.Name().c_str(), i); + Options["sourceslist-entry"] = entry; + } + + // now create one item per suite/section + auto const list_uris = FindMultiValue(Tags, "URIs"); + auto const list_comp = FindMultiValue(Tags, "Components"); + auto list_suite = FindMultiValue(Tags, "Suites"); + { + auto const nativeArch = _config->Find("APT::Architecture"); + std::transform(list_suite.begin(), list_suite.end(), list_suite.begin(), + [&](std::string const &suite) { return SubstVar(suite, "$(ARCH)", nativeArch); }); + } + + if (list_uris.empty()) + // TRANSLATOR: %u is a line number, the first %s is a filename of a file with the extension "second %s" and the third %s is a unique identifier for bugreports + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), i, "sources", Fd.Name().c_str(), "URI"); + + if (list_suite.empty()) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), i, "sources", Fd.Name().c_str(), "Suite"); + + for (auto URI : list_uris) + { + if (FixupURI(URI) == false) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), i, "sources", Fd.Name().c_str(), "URI parse"); + + for (auto const &S : list_suite) + { + if (likely(S.empty() == false) && S[S.size() - 1] == '/') + { + if (list_comp.empty() == false) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), i, "sources", Fd.Name().c_str(), "absolute Suite Component"); + if (CreateItem(List, URI, S, "", Options) == false) + return false; + } + else + { + if (list_comp.empty()) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), i, "sources", Fd.Name().c_str(), "Component"); + + for (auto const &C : list_comp) + if (CreateItem(List, URI, S, C, Options) == false) + return false; + } + } + } + return true; +} + /*}}}*/ +// Type::ParseLine - Parse a single line /*{{{*/ +// --------------------------------------------------------------------- +/* This is a generic one that is the 'usual' format for sources.list + Weird types may override this. */ +bool pkgSourceList::Type::ParseLine(vector<metaIndex *> &List, + const char *Buffer, + unsigned int const CurLine, + string const &File) const +{ + for (;Buffer != 0 && isspace(*Buffer); ++Buffer); // Skip whitespaces + + // Parse option field if it exists + // e.g.: [ option1=value1 option2=value2 ] + map<string, string> Options; + { + std::string entry; + strprintf(entry, "%s:%i", File.c_str(), CurLine); + Options["sourceslist-entry"] = entry; + } + if (Buffer != 0 && Buffer[0] == '[') + { + ++Buffer; // ignore the [ + for (;Buffer != 0 && isspace(*Buffer); ++Buffer); // Skip whitespaces + while (*Buffer != ']') + { + // get one option, e.g. option1=value1 + string option; + if (ParseQuoteWord(Buffer,option) == false) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), CurLine, "list", File.c_str(), "[option] unparseable"); + + if (option.length() < 3) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), CurLine, "list", File.c_str(), "[option] too short"); + + // accept options even if the last has no space before the ]-end marker + if (option.at(option.length()-1) == ']') + { + for (; *Buffer != ']'; --Buffer); + option.resize(option.length()-1); + } + + size_t const needle = option.find('='); + if (needle == string::npos) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), CurLine, "list", File.c_str(), "[option] not assignment"); + + string const key = string(option, 0, needle); + string const value = string(option, needle + 1, option.length()); + + if (key.empty() == true) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), CurLine, "list", File.c_str(), "[option] no key"); + + if (value.empty() == true) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), CurLine, "list", File.c_str(), "[option] no value"); + + Options[key] = value; + } + ++Buffer; // ignore the ] + for (;Buffer != 0 && isspace(*Buffer); ++Buffer); // Skip whitespaces + } + + string URI; + string Dist; + string Section; + + if (ParseQuoteWord(Buffer,URI) == false) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), CurLine, "list", File.c_str(), "URI"); + if (ParseQuoteWord(Buffer,Dist) == false) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), CurLine, "list", File.c_str(), "Suite"); + + if (FixupURI(URI) == false) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), CurLine, "list", File.c_str(), "URI parse"); + + // Check for an absolute dists specification. + if (Dist.empty() == false && Dist[Dist.size() - 1] == '/') + { + if (ParseQuoteWord(Buffer,Section) == true) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), CurLine, "list", File.c_str(), "absolute Suite Component"); + Dist = SubstVar(Dist,"$(ARCH)",_config->Find("APT::Architecture")); + return CreateItem(List, URI, Dist, Section, Options); + } + + // Grab the rest of the dists + if (ParseQuoteWord(Buffer,Section) == false) + return _error->Error(_("Malformed entry %u in %s file %s (%s)"), CurLine, "list", File.c_str(), "Component"); + + do + { + if (CreateItem(List, URI, Dist, Section, Options) == false) + return false; + } + while (ParseQuoteWord(Buffer,Section) == true); + + return true; +} + /*}}}*/ +// SourceList::pkgSourceList - Constructors /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgSourceList::pkgSourceList() : d(NULL) +{ +} + /*}}}*/ +// SourceList::~pkgSourceList - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgSourceList::~pkgSourceList() +{ + for (const_iterator I = SrcList.begin(); I != SrcList.end(); ++I) + delete *I; + SrcList.clear(); + for (auto F = VolatileFiles.begin(); F != VolatileFiles.end(); ++F) + delete (*F); + VolatileFiles.clear(); +} + /*}}}*/ +// SourceList::ReadMainList - Read the main source list from etc /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgSourceList::ReadMainList() +{ + Reset(); + // CNC:2003-11-28 - Entries in sources.list have priority over + // entries in sources.list.d. + string Main = _config->FindFile("Dir::Etc::sourcelist", "/dev/null"); + string Parts = _config->FindDir("Dir::Etc::sourceparts", "/dev/null"); + + _error->PushToStack(); + if (RealFileExists(Main) == true) + ReadAppend(Main); + else if (DirectoryExists(Parts) == false && APT::String::Endswith(Parts, "/dev/null") == false) + // Only warn if there are no sources.list.d. + _error->WarningE("DirectoryExists", _("Unable to read %s"), Parts.c_str()); + + if (DirectoryExists(Parts) == true) + ReadSourceDir(Parts); + else if (Main.empty() == false && RealFileExists(Main) == false && + APT::String::Endswith(Parts, "/dev/null") == false) + // Only warn if there is no sources.list file. + _error->WarningE("RealFileExists", _("Unable to read %s"), Main.c_str()); + + for (auto && file: _config->FindVector("APT::Sources::With")) + AddVolatileFile(file, nullptr); + + auto good = _error->PendingError() == false; + _error->MergeWithStack(); + return good; +} + /*}}}*/ +// SourceList::Reset - Clear the sourcelist contents /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgSourceList::Reset() +{ + for (const_iterator I = SrcList.begin(); I != SrcList.end(); ++I) + delete *I; + SrcList.clear(); +} + /*}}}*/ +// SourceList::Read - Parse the sourcelist file /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgSourceList::Read(string const &File) +{ + Reset(); + return ReadAppend(File); +} + /*}}}*/ +// SourceList::ReadAppend - Parse a sourcelist file /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgSourceList::ReadAppend(string const &File) +{ + if (flExtension(File) == "sources") + return ParseFileDeb822(File); + else + return ParseFileOldStyle(File); +} + /*}}}*/ +// SourceList::ReadFileOldStyle - Read Traditional style sources.list /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgSourceList::ParseFileOldStyle(std::string const &File) +{ + FileFd Fd; + if (OpenConfigurationFileFd(File, Fd) == false) + return false; + + std::string Buffer; + for (unsigned int CurLine = 1; Fd.ReadLine(Buffer); ++CurLine) + { + // remove comments + size_t curpos = 0; + while ((curpos = Buffer.find('#', curpos)) != std::string::npos) + { + size_t const openbrackets = std::count(Buffer.begin(), Buffer.begin() + curpos, '['); + size_t const closedbrackets = std::count(Buffer.begin(), Buffer.begin() + curpos, ']'); + if (openbrackets > closedbrackets) + { + // a # in an option, unlikely, but oh well, it was supported so stick to it + ++curpos; + continue; + } + Buffer.erase(curpos); + break; + } + // remove spaces before/after + curpos = Buffer.find_first_not_of(" \t\r"); + if (curpos != 0) + Buffer.erase(0, curpos); + curpos = Buffer.find_last_not_of(" \t\r"); + if (curpos != std::string::npos) + Buffer.erase(curpos + 1); + + if (Buffer.empty()) + continue; + + // Grok it + std::string const LineType = Buffer.substr(0, Buffer.find_first_of(" \t\v")); + if (LineType.empty() || LineType == Buffer) + return _error->Error(_("Malformed line %u in source list %s (type)"),CurLine,File.c_str()); + + Type *Parse = Type::GetType(LineType.c_str()); + if (Parse == 0) + return _error->Error(_("Type '%s' is not known on line %u in source list %s"),LineType.c_str(),CurLine,File.c_str()); + + if (Parse->ParseLine(SrcList, Buffer.c_str() + LineType.length(), CurLine, File) == false) + return false; + } + return true; +} + /*}}}*/ +// SourceList::ParseFileDeb822 - Parse deb822 style sources.list /*{{{*/ +// --------------------------------------------------------------------- +/* Returns: the number of stanzas parsed*/ +bool pkgSourceList::ParseFileDeb822(string const &File) +{ + // see if we can read the file + FileFd Fd; + if (OpenConfigurationFileFd(File, Fd) == false) + return false; + pkgTagFile Sources(&Fd, pkgTagFile::SUPPORT_COMMENTS); + if (Fd.IsOpen() == false || Fd.Failed()) + return _error->Error(_("Malformed stanza %u in source list %s (type)"),0,File.c_str()); + + // read step by step + pkgTagSection Tags; + unsigned int i = 0; + while (Sources.Step(Tags) == true) + { + ++i; + if(Tags.Exists("Types") == false) + return _error->Error(_("Malformed stanza %u in source list %s (type)"),i,File.c_str()); + + for (auto const &type : FindMultiValue(Tags, "Types")) + { + Type *Parse = Type::GetType(type.c_str()); + if (Parse == 0) + { + _error->Error(_("Type '%s' is not known on stanza %u in source list %s"), type.c_str(), i, Fd.Name().c_str()); + return false; + } + + if (!Parse->ParseStanza(SrcList, Tags, i, Fd)) + return false; + } + } + return true; +} + /*}}}*/ +// SourceList::FindIndex - Get the index associated with a file /*{{{*/ +static bool FindInIndexFileContainer(std::vector<pkgIndexFile *> const &Cont, pkgCache::PkgFileIterator const &File, pkgIndexFile *&Found) +{ + auto const J = std::find_if(Cont.begin(), Cont.end(), [&File](pkgIndexFile const * const J) { + return J->FindInCache(*File.Cache()) == File; + }); + if (J != Cont.end()) + { + Found = (*J); + return true; + } + return false; +} +bool pkgSourceList::FindIndex(pkgCache::PkgFileIterator File, + pkgIndexFile *&Found) const +{ + for (const_iterator I = SrcList.begin(); I != SrcList.end(); ++I) + if (FindInIndexFileContainer(*(*I)->GetIndexFiles(), File, Found)) + return true; + + return FindInIndexFileContainer(VolatileFiles, File, Found); +} + /*}}}*/ +// SourceList::GetIndexes - Load the index files into the downloader /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgSourceList::GetIndexes(pkgAcquire *Owner, bool GetAll) const +{ + for (const_iterator I = SrcList.begin(); I != SrcList.end(); ++I) + if ((*I)->GetIndexes(Owner,GetAll) == false) + return false; + return true; +} + /*}}}*/ +// CNC:2003-03-03 - By Anton V. Denisov <avd@altlinux.org>. +// SourceList::ReadSourceDir - Read a directory with sources files +// Based on ReadConfigDir() /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgSourceList::ReadSourceDir(string const &Dir) +{ + std::vector<std::string> const ext = {"list", "sources"}; + // Read the files + bool good = true; + for (auto const &I : GetListOfFilesInDir(Dir, ext, true)) + good = ReadAppend(I) && good; + return good; +} + /*}}}*/ +// GetLastModified() /*{{{*/ +// --------------------------------------------------------------------- +/* */ +time_t pkgSourceList::GetLastModifiedTime() +{ + vector<string> List; + + string Main = _config->FindFile("Dir::Etc::sourcelist"); + string Parts = _config->FindDir("Dir::Etc::sourceparts"); + + // go over the parts + if (DirectoryExists(Parts) == true) + List = GetListOfFilesInDir(Parts, "list", true); + + // calculate the time + std::vector<time_t> modtimes; + modtimes.reserve(1 + List.size()); + modtimes.push_back(GetModificationTime(Main)); + std::transform(List.begin(), List.end(), std::back_inserter(modtimes), GetModificationTime); + auto const maxmtime = std::max_element(modtimes.begin(), modtimes.end()); + return *maxmtime; +} + /*}}}*/ +std::vector<pkgIndexFile*> pkgSourceList::GetVolatileFiles() const /*{{{*/ +{ + return VolatileFiles; +} + /*}}}*/ +void pkgSourceList::AddVolatileFile(pkgIndexFile * const File) /*{{{*/ +{ + if (File != nullptr) + VolatileFiles.push_back(File); +} + /*}}}*/ +static bool fileNameMatches(std::string const &filename, std::string const &idxtype)/*{{{*/ +{ + for (auto && type: APT::Configuration::getCompressionTypes()) + { + if (type == "uncompressed") + { + if (filename == idxtype || APT::String::Endswith(filename, '_' + idxtype)) + return true; + } + else if (filename == idxtype + '.' + type || + APT::String::Endswith(filename, '_' + idxtype + '.' + type)) + return true; + } + return false; +} + /*}}}*/ +bool pkgSourceList::AddVolatileFile(std::string const &File, std::vector<std::string> * const VolatileCmdL)/*{{{*/ +{ + // Note: FileExists matches directories and links, too! + if (File.empty() || FileExists(File) == false) + return false; + + std::string const ext = flExtension(File); + // udeb is not included as installing it is usually a mistake rather than intended + if (ext == "deb" || ext == "ddeb") + AddVolatileFile(new debDebPkgFileIndex(File)); + else if (ext == "dsc") + AddVolatileFile(new debDscFileIndex(File)); + else if (FileExists(flCombine(File, "debian/control"))) + AddVolatileFile(new debDscFileIndex(flCombine(File, "debian/control"))); + else if (ext == "changes") + { + debDscRecordParser changes(File, nullptr); + std::vector<pkgSrcRecords::File2> fileslst; + if (changes.Files2(fileslst) == false || fileslst.empty()) + return false; + auto const basedir = flNotFile(File); + for (auto && file: fileslst) + { + auto const name = flCombine(basedir, file.Path); + AddVolatileFile(name, VolatileCmdL); + if (file.Hashes.VerifyFile(name) == false) + return _error->Error("The file %s does not match with the hashes in the %s file!", name.c_str(), File.c_str()); + } + return true; + } + else + { + auto const filename = flNotDir(File); + auto const Target = IndexTarget(File, filename, File, "file:" + File, false, true, { + { "FILENAME", File }, + { "REPO_URI", "file:" + flAbsPath(flNotFile(File)) + '/' }, + { "COMPONENT", "volatile-packages-file" }, + }); + if (fileNameMatches(filename, "Packages")) + AddVolatileFile(new debPackagesIndex(Target, true)); + else if (fileNameMatches(filename, "Sources")) + AddVolatileFile(new debSourcesIndex(Target, true)); + else + return false; + } + + if (VolatileCmdL != nullptr) + VolatileCmdL->push_back(File); + return true; +} +bool pkgSourceList::AddVolatileFile(std::string const &File) +{ + return AddVolatileFile(File, nullptr); +} + /*}}}*/ +void pkgSourceList::AddVolatileFiles(CommandLine &CmdL, std::vector<std::string> * const VolatileCmdL)/*{{{*/ +{ + std::remove_if(CmdL.FileList + 1, CmdL.FileList + 1 + CmdL.FileSize(), [&](char const * const I) { + if (I != nullptr && (I[0] == '/' || (I[0] == '.' && (I[1] == '\0' || (I[1] == '.' && (I[2] == '\0' || I[2] == '/')) || I[1] == '/')))) + { + if (AddVolatileFile(I, VolatileCmdL)) + ; + else + _error->Error(_("Unsupported file %s given on commandline"), I); + return true; + } + return false; + }); +} +void pkgSourceList::AddVolatileFiles(CommandLine &CmdL, std::vector<const char*> * const VolatileCmdL) +{ + std::remove_if(CmdL.FileList + 1, CmdL.FileList + 1 + CmdL.FileSize(), [&](char const * const I) { + if (I != nullptr && (I[0] == '/' || (I[0] == '.' && (I[1] == '\0' || (I[1] == '.' && (I[2] == '\0' || I[2] == '/')) || I[1] == '/')))) + { + if (AddVolatileFile(I)) + { + if (VolatileCmdL != nullptr) + VolatileCmdL->push_back(I); + } + else + _error->Error(_("Unsupported file %s given on commandline"), I); + return true; + } + return false; + }); +} + /*}}}*/ diff --git a/apt-pkg/sourcelist.h b/apt-pkg/sourcelist.h new file mode 100644 index 0000000..fedf90f --- /dev/null +++ b/apt-pkg/sourcelist.h @@ -0,0 +1,144 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + SourceList - Manage a list of sources + + The Source List class provides access to a list of sources. It + can read them from a file and generate a list of all the distinct + sources. + + All sources have a type associated with them that defines the layout + of the archive. The exact format of the file is documented in + files.sgml. + + The types are mapped through a list of type definitions which handle + the actual construction of the back end type. After loading a source + list all you have is a list of package index files that have the ability + to be Acquired. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_SOURCELIST_H +#define PKGLIB_SOURCELIST_H + +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#include <time.h> + +#include <map> +#include <string> +#include <vector> + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/tagfile.h> +#endif +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/metaindex.h> +using std::string; +using std::vector; +#endif + +class FileFd; +class pkgTagSection; +class pkgAcquire; +class pkgIndexFile; +class metaIndex; +class CommandLine; + +class pkgSourceList +{ + void * const d; + std::vector<pkgIndexFile*> VolatileFiles; + public: + + // List of supported source list types + class Type + { + public: + + // Global list of Items supported + static Type **GlobalList; + static unsigned long GlobalListLen; + static Type *GetType(const char *Type) APT_PURE; + + char const * const Name; + char const * const Label; + + bool FixupURI(std::string &URI) const; + virtual bool ParseStanza(std::vector<metaIndex *> &List, + pkgTagSection &Tags, + unsigned int const stanza_n, + FileFd &Fd); + virtual bool ParseLine(std::vector<metaIndex *> &List, + const char *Buffer, + unsigned int const CurLine,std::string const &File) const; + virtual bool CreateItem(std::vector<metaIndex *> &List,std::string const &URI, + std::string const &Dist,std::string const &Section, + std::map<std::string, std::string> const &Options) const = 0; + Type(char const * const Name, char const * const Label); + virtual ~Type(); + }; + + typedef std::vector<metaIndex *>::const_iterator const_iterator; + + protected: + + std::vector<metaIndex *> SrcList; + + private: + APT_HIDDEN bool ParseFileDeb822(std::string const &File); + APT_HIDDEN bool ParseFileOldStyle(std::string const &File); + + public: + + bool ReadMainList(); + bool Read(std::string const &File); + + // CNC:2003-03-03 + void Reset(); + bool ReadAppend(std::string const &File); + bool ReadSourceDir(std::string const &Dir); + + // List accessors + inline const_iterator begin() const {return SrcList.begin();}; + inline const_iterator end() const {return SrcList.end();}; + inline unsigned int size() const {return SrcList.size();}; + inline bool empty() const {return SrcList.empty();}; + + bool FindIndex(pkgCache::PkgFileIterator File, + pkgIndexFile *&Found) const; + bool GetIndexes(pkgAcquire *Owner, bool GetAll=false) const; + + // query last-modified time + time_t GetLastModifiedTime(); + + /** \brief add file for parsing, but not to the cache + * + * pkgIndexFiles origining from pkgSourcesList are included in + * srcpkgcache, the status files added via #AddStatusFiles are + * included in pkgcache, but these files here are not included in + * any cache to have the possibility of having a file included just + * for a single run like a local .deb/.dsc file. + * + * The volatile files do not count as "normal" sourceslist entries, + * can't be iterated over with #begin and #end and can't be + * downloaded, but they can be found via #FindIndex. + * + * @param File is an index file; pointer-ownership is transferred + */ + void AddVolatileFile(pkgIndexFile * const File); + bool AddVolatileFile(std::string const &File); + bool AddVolatileFile(std::string const &File, std::vector<std::string> * const VolatileCmdL); + APT_DEPRECATED_MSG("Use the overload with string-vector") void AddVolatileFiles(CommandLine &CmdL, std::vector<const char*> * const VolatileCmdL); + void AddVolatileFiles(CommandLine &CmdL, std::vector<std::string> * const VolatileCmdL); + + /** @return list of files registered with #AddVolatileFile */ + std::vector<pkgIndexFile*> GetVolatileFiles() const; + + pkgSourceList(); + virtual ~pkgSourceList(); +}; + +#endif diff --git a/apt-pkg/srcrecords.cc b/apt-pkg/srcrecords.cc new file mode 100644 index 0000000..9d515ae --- /dev/null +++ b/apt-pkg/srcrecords.cc @@ -0,0 +1,182 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Source Package Records - Allows access to source package records + + Parses and allows access to the list of source records and searching by + source name on that list. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/debsrcrecords.h> +#include <apt-pkg/error.h> +#include <apt-pkg/indexfile.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/metaindex.h> +#include <apt-pkg/sourcelist.h> +#include <apt-pkg/srcrecords.h> + +#include <string> +#include <vector> +#include <string.h> + +#include <apti18n.h> + /*}}}*/ + +// SrcRecords::pkgSrcRecords - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* Open all the source index files */ +pkgSrcRecords::pkgSrcRecords(pkgSourceList &List) : d(NULL), Files(0) +{ + for (pkgSourceList::const_iterator I = List.begin(); I != List.end(); ++I) + { + std::vector<pkgIndexFile *> *Indexes = (*I)->GetIndexFiles(); + for (std::vector<pkgIndexFile *>::const_iterator J = Indexes->begin(); + J != Indexes->end(); ++J) + { + _error->PushToStack(); + Parser* P = (*J)->CreateSrcParser(); + bool const newError = _error->PendingError(); + _error->MergeWithStack(); + if (newError) + return; + if (P != 0) + Files.push_back(P); + } + } + + // Doesn't work without any source index files + if (Files.empty() == true) + { + _error->Error(_("You must put some 'source' URIs" + " in your sources.list")); + return; + } + + Restart(); +} + /*}}}*/ +// SrcRecords::~pkgSrcRecords - Destructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgSrcRecords::~pkgSrcRecords() +{ + // Blow away all the parser objects + for(std::vector<Parser*>::iterator I = Files.begin(); I != Files.end(); ++I) + delete *I; +} + /*}}}*/ +// SrcRecords::Restart - Restart the search /*{{{*/ +// --------------------------------------------------------------------- +/* Return all of the parsers to their starting position */ +bool pkgSrcRecords::Restart() +{ + Current = Files.begin(); + for (std::vector<Parser*>::iterator I = Files.begin(); + I != Files.end(); ++I) + if ((*I)->Offset() != 0) + (*I)->Restart(); + + return true; +} + /*}}}*/ +// SrcRecords::Step - Step to the next Source Record /*{{{*/ +// --------------------------------------------------------------------- +/* Step to the next source package record */ +const pkgSrcRecords::Parser* pkgSrcRecords::Step() +{ + if (Current == Files.end()) + return 0; + + // Step to the next record, possibly switching files + while ((*Current)->Step() == false) + { + ++Current; + if (Current == Files.end()) + return 0; + } + + return *Current; +} + /*}}}*/ +// SrcRecords::Find - Find the first source package with the given name /*{{{*/ +// --------------------------------------------------------------------- +/* This searches on both source package names and output binary names and + returns the first found. A 'cursor' like system is used to allow this + function to be called multiple times to get successive entries */ +pkgSrcRecords::Parser *pkgSrcRecords::Find(const char *Package,bool const &SrcOnly) +{ + while (true) + { + if(Step() == 0) + return 0; + + // Source name hit + if ((*Current)->Package() == Package) + return *Current; + + if (SrcOnly == true) + continue; + + // Check for a binary hit + const char **I = (*Current)->Binaries(); + for (; I != 0 && *I != 0; ++I) + if (strcmp(Package,*I) == 0) + return *Current; + } +} + /*}}}*/ +// Parser::BuildDepType - Convert a build dep to a string /*{{{*/ +// --------------------------------------------------------------------- +/* */ +const char *pkgSrcRecords::Parser::BuildDepType(unsigned char const &Type) +{ + const char *fields[] = {"Build-Depends", + "Build-Depends-Indep", + "Build-Conflicts", + "Build-Conflicts-Indep", + "Build-Depends-Arch", + "Build-Conflicts-Arch"}; + if (unlikely(Type >= sizeof(fields)/sizeof(fields[0]))) + return ""; + return fields[Type]; +} + /*}}}*/ +bool pkgSrcRecords::Parser::Files2(std::vector<pkgSrcRecords::File2> &F2)/*{{{*/ +{ + debSrcRecordParser * const deb = dynamic_cast<debSrcRecordParser*>(this); + if (deb != NULL) + return deb->Files2(F2); + + std::vector<pkgSrcRecords::File> F; + if (Files(F) == false) + return false; + for (std::vector<pkgSrcRecords::File>::const_iterator f = F.begin(); f != F.end(); ++f) + { + pkgSrcRecords::File2 f2; +#if __GNUC__ >= 4 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + f2.MD5Hash = f->MD5Hash; + f2.Size = f->Size; + f2.Hashes.push_back(HashString("MD5Sum", f->MD5Hash)); + f2.FileSize = f->Size; +#if __GNUC__ >= 4 + #pragma GCC diagnostic pop +#endif + f2.Path = f->Path; + f2.Type = f->Type; + F2.push_back(f2); + } + return true; +} + /*}}}*/ + + +pkgSrcRecords::Parser::Parser(const pkgIndexFile *Index) : d(NULL), iIndex(Index) {} +pkgSrcRecords::Parser::~Parser() {} diff --git a/apt-pkg/srcrecords.h b/apt-pkg/srcrecords.h new file mode 100644 index 0000000..8c52f56 --- /dev/null +++ b/apt-pkg/srcrecords.h @@ -0,0 +1,120 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Source Package Records - Allows access to source package records + + Parses and allows access to the list of source records and searching by + source name on that list. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_SRCRECORDS_H +#define PKGLIB_SRCRECORDS_H + +#include <apt-pkg/hashes.h> +#include <apt-pkg/macros.h> + +#include <string> +#include <vector> + +#ifndef APT_8_CLEANER_HEADERS +using std::string; +using std::vector; +#endif + +class pkgSourceList; +class pkgIndexFile; +class pkgSrcRecords +{ + public: + +APT_IGNORE_DEPRECATED_PUSH + // Describes a single file + struct File + { + APT_DEPRECATED_MSG("Use Hashes member instead of hardcoded hash algorithm") std::string MD5Hash; + APT_DEPRECATED_MSG("Use FileSize member instead") unsigned long Size; + std::string Path; + std::string Type; + }; + struct File2 : public File + { + unsigned long long FileSize; + HashStringList Hashes; + }; +APT_IGNORE_DEPRECATED_POP + + // Abstract parser for each source record + class Parser + { + void * const d; + protected: + + const pkgIndexFile *iIndex; + + public: + + enum BuildDep {BuildDepend=0x0,BuildDependIndep=0x1, + BuildConflict=0x2,BuildConflictIndep=0x3, + BuildDependArch=0x4,BuildConflictArch=0x5}; + + struct BuildDepRec + { + std::string Package; + std::string Version; + unsigned int Op; + unsigned char Type; + }; + + inline const pkgIndexFile &Index() const {return *iIndex;}; + + virtual bool Restart() = 0; + virtual bool Step() = 0; + virtual bool Jump(unsigned long const &Off) = 0; + virtual unsigned long Offset() = 0; + virtual std::string AsStr() = 0; + + virtual std::string Package() const = 0; + virtual std::string Version() const = 0; + virtual std::string Maintainer() const = 0; + virtual std::string Section() const = 0; + virtual const char **Binaries() = 0; // Ownership does not transfer + + //FIXME: Add a parameter to specify which architecture to use for [wildcard] matching + virtual bool BuildDepends(std::vector<BuildDepRec> &BuildDeps, bool const &ArchOnly, bool const &StripMultiArch = true) = 0; + static const char *BuildDepType(unsigned char const &Type) APT_PURE; + + virtual bool Files(std::vector<pkgSrcRecords::File> &F) = 0; + bool Files2(std::vector<pkgSrcRecords::File2> &F); + + explicit Parser(const pkgIndexFile *Index); + virtual ~Parser(); + }; + + private: + /** \brief dpointer placeholder (for later in case we need it) */ + void * const d; + + // The list of files and the current parser pointer + std::vector<Parser*> Files; + std::vector<Parser *>::iterator Current; + + public: + + // Reset the search + bool Restart(); + + // Step to the next SourcePackage and return pointer to the + // next SourceRecord. The pointer is owned by libapt. + const Parser* Step(); + + // Locate a package by name and return pointer to the Parser. + // The pointer is owned by libapt. + Parser* Find(const char *Package,bool const &SrcOnly = false); + + explicit pkgSrcRecords(pkgSourceList &List); + virtual ~pkgSrcRecords(); +}; + +#endif diff --git a/apt-pkg/statechanges.cc b/apt-pkg/statechanges.cc new file mode 100644 index 0000000..258c848 --- /dev/null +++ b/apt-pkg/statechanges.cc @@ -0,0 +1,227 @@ +#include <apt-pkg/cacheset.h> +#include <apt-pkg/debsystem.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/prettyprinters.h> +#include <apt-pkg/statechanges.h> + +#include <algorithm> +#include <memory> + +namespace APT +{ + +class StateChanges::Private +{ +public: + APT::VersionVector hold; + APT::VersionVector unhold; + APT::VersionVector install; + APT::VersionVector deinstall; + APT::VersionVector purge; + APT::VersionVector error; +}; + +#define APT_GETTERSETTER(Name, Container) \ +void StateChanges::Name(pkgCache::VerIterator const &Ver) \ +{ \ + if (Ver.end() == false) \ + Container.push_back(Ver); \ +}\ +APT::VersionVector& StateChanges::Name() \ +{ \ + return Container; \ +} +APT_GETTERSETTER(Hold, d->hold) +APT_GETTERSETTER(Unhold, d->unhold) +APT_GETTERSETTER(Install, d->install) +APT_GETTERSETTER(Remove, d->deinstall) +APT_GETTERSETTER(Purge, d->purge) +#undef APT_GETTERSETTER +APT::VersionVector& StateChanges::Error() +{ + return d->error; +} + +void StateChanges::clear() +{ + d->hold.clear(); + d->unhold.clear(); + d->install.clear(); + d->deinstall.clear(); + d->purge.clear(); + d->error.clear(); +} + +bool StateChanges::empty() const +{ + return d->hold.empty() && + d->unhold.empty() && + d->install.empty() && + d->deinstall.empty() && + d->purge.empty() && + d->error.empty(); +} + +bool StateChanges::Save(bool const DiscardOutput) +{ + bool const Debug = _config->FindB("Debug::pkgDpkgPm", false); + d->error.clear(); + if (d->hold.empty() && d->unhold.empty() && d->install.empty() && d->deinstall.empty() && d->purge.empty()) + return true; + + std::vector<std::string> Args = debSystem::GetDpkgBaseCommand(); + // ensure dpkg knows about the package so that it keeps the status we set + if (d->hold.empty() == false || d->install.empty() == false) + { + APT::VersionVector makeDpkgAvailable; + auto const notInstalled = [](pkgCache::VerIterator const &V) { return V.ParentPkg()->CurrentVer == 0; }; + std::copy_if(d->hold.begin(), d->hold.end(), std::back_inserter(makeDpkgAvailable), notInstalled); + std::copy_if(d->install.begin(), d->install.end(), std::back_inserter(makeDpkgAvailable), notInstalled); + + if (makeDpkgAvailable.empty() == false) + { + auto const BaseArgs = Args.size(); + Args.push_back("--merge-avail"); + // FIXME: supported only since 1.17.7 in dpkg + Args.push_back("-"); + int dummyAvail = -1; + if (Debug) + { + for (auto const &V: makeDpkgAvailable) + { + std::clog << "echo 'Dummy record for " << V.ParentPkg().FullName(false) << "' | "; + std::copy(Args.begin(), Args.end(), std::ostream_iterator<std::string>(std::clog, " ")); + std::clog << std::endl; + } + } + else + { + pid_t const dpkgMergeAvail = debSystem::ExecDpkg(Args, &dummyAvail, nullptr, true); + + FILE* dpkg = fdopen(dummyAvail, "w"); + for (auto const &V: makeDpkgAvailable) + fprintf(dpkg, "Package: %s\nVersion: 0~\nArchitecture: %s\nMaintainer: Dummy Example <dummy@example.org>\n" + "Description: dummy package record\n A record is needed to put a package on hold, so here it is.\n\n", V.ParentPkg().Name(), V.Arch()); + fclose(dpkg); + + ExecWait(dpkgMergeAvail, "dpkg --merge-avail", true); + } + Args.erase(Args.begin() + BaseArgs, Args.end()); + } + } + bool const dpkgMultiArch = _system->MultiArchSupported(); + + Args.push_back("--set-selections"); + if (Debug) + { + std::string state; + auto const dpkgName = [&](pkgCache::VerIterator const &V) { + pkgCache::PkgIterator P = V.ParentPkg(); + if (strcmp(V.Arch(), "none") == 0) + ioprintf(std::clog, "echo '%s %s' | ", P.Name(), state.c_str()); + else if (dpkgMultiArch == false) + ioprintf(std::clog, "echo '%s %s' | ", P.FullName(true).c_str(), state.c_str()); + else + ioprintf(std::clog, "echo '%s:%s %s' | ", P.Name(), V.Arch(), state.c_str()); + std::copy(Args.begin(), Args.end(), std::ostream_iterator<std::string>(std::clog, " ")); + std::clog << std::endl; + }; + for (auto const &V: d->unhold) + { + if (V.ParentPkg()->CurrentVer != 0) + state = "install"; + else + state = "deinstall"; + dpkgName(V); + } + if (d->purge.empty() == false) + { + state = "purge"; + std::for_each(d->purge.begin(), d->purge.end(), dpkgName); + } + if (d->deinstall.empty() == false) + { + state = "deinstall"; + std::for_each(d->deinstall.begin(), d->deinstall.end(), dpkgName); + } + if (d->hold.empty() == false) + { + state = "hold"; + std::for_each(d->hold.begin(), d->hold.end(), dpkgName); + } + if (d->install.empty() == false) + { + state = "install"; + std::for_each(d->install.begin(), d->install.end(), dpkgName); + } + } + else + { + int selections = -1; + pid_t const dpkgSelections = debSystem::ExecDpkg(Args, &selections, nullptr, DiscardOutput); + + FILE* dpkg = fdopen(selections, "w"); + std::string state; + auto const dpkgName = [&](pkgCache::VerIterator const &V) { + pkgCache::PkgIterator P = V.ParentPkg(); + if (strcmp(V.Arch(), "none") == 0) + fprintf(dpkg, "%s %s\n", P.Name(), state.c_str()); + else if (dpkgMultiArch == false) + fprintf(dpkg, "%s %s\n", P.FullName(true).c_str(), state.c_str()); + else + fprintf(dpkg, "%s:%s %s\n", P.Name(), V.Arch(), state.c_str()); + }; + for (auto const &V: d->unhold) + { + if (V.ParentPkg()->CurrentVer != 0) + state = "install"; + else + state = "deinstall"; + dpkgName(V); + } + if (d->purge.empty() == false) + { + state = "purge"; + std::for_each(d->purge.begin(), d->purge.end(), dpkgName); + } + if (d->deinstall.empty() == false) + { + state = "deinstall"; + std::for_each(d->deinstall.begin(), d->deinstall.end(), dpkgName); + } + if (d->hold.empty() == false) + { + state = "hold"; + std::for_each(d->hold.begin(), d->hold.end(), dpkgName); + } + if (d->install.empty() == false) + { + state = "install"; + std::for_each(d->install.begin(), d->install.end(), dpkgName); + } + fclose(dpkg); + + if (ExecWait(dpkgSelections, "dpkg --set-selections") == false) + { + std::move(d->purge.begin(), d->purge.end(), std::back_inserter(d->error)); + d->purge.clear(); + std::move(d->deinstall.begin(), d->deinstall.end(), std::back_inserter(d->error)); + d->deinstall.clear(); + std::move(d->hold.begin(), d->hold.end(), std::back_inserter(d->error)); + d->hold.clear(); + std::move(d->unhold.begin(), d->unhold.end(), std::back_inserter(d->error)); + d->unhold.clear(); + std::move(d->install.begin(), d->install.end(), std::back_inserter(d->error)); + d->install.clear(); + } + } + return d->error.empty(); +} + +StateChanges::StateChanges() : d(new StateChanges::Private()) {} +StateChanges::StateChanges(StateChanges&&) = default; +StateChanges& StateChanges::operator=(StateChanges&&) = default; +StateChanges::~StateChanges() = default; + +} diff --git a/apt-pkg/statechanges.h b/apt-pkg/statechanges.h new file mode 100644 index 0000000..2f63d51 --- /dev/null +++ b/apt-pkg/statechanges.h @@ -0,0 +1,58 @@ +#include <apt-pkg/cacheset.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/pkgcache.h> + +#include <memory> + +namespace APT +{ + +/** Simple wrapper class to abstract away the differences in storing different + * states in different places potentially in different versions. + */ +class APT_PUBLIC StateChanges +{ +public: + // getter/setter for the different states +#define APT_GETTERSETTER(Name) \ + APT::VersionVector& Name(); \ + void Name(pkgCache::VerIterator const &Ver) + APT_GETTERSETTER(Hold); + APT_GETTERSETTER(Unhold); + APT_GETTERSETTER(Install); + APT_GETTERSETTER(Remove); + APT_GETTERSETTER(Purge); + APT::VersionVector& Error(); +#undef APT_GETTERSETTER + + // operate on all containers at once + void clear(); + bool empty() const; + + /** commit the staged changes to the database(s). + * + * Makes the needed calls to store the requested states. + * After this call the state containers will hold only versions + * for which the storing operation succeeded. Versions where the + * storing operation failed are collected in #Error(). Note that + * error is an upper bound as states are changed in batches so it + * isn't always clear which version triggered the failure exactly. + * + * @param DiscardOutput controls if stdout/stderr should be used + * by subprocesses for (detailed) error reporting if needed. + * @return \b false if storing failed, true otherwise. + * Note that some states might be applied even if the whole operation failed. + */ + bool Save(bool const DiscardOutput = false); + + StateChanges(); + StateChanges(StateChanges&&); + StateChanges& operator=(StateChanges&&); + ~StateChanges(); + +private: + class APT_HIDDEN Private; + std::unique_ptr<Private> d; +}; + +} diff --git a/apt-pkg/tagfile-compat.cc b/apt-pkg/tagfile-compat.cc new file mode 100644 index 0000000..1e1d23e --- /dev/null +++ b/apt-pkg/tagfile-compat.cc @@ -0,0 +1,75 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Fast scanner for RFC-822 type header information + + This uses a rotating buffer to load the package information into. + The scanner runs over it and isolates and indexes a single section. + + This defines compat functions for the external code. + + ##################################################################### */ + /*}}}*/ + +#include <config.h> +#define APT_COMPILING_TAGFILE_COMPAT_CC +#include <apt-pkg/tagfile.h> + +using std::string; +using APT::StringView; + + +bool pkgTagSection::Exists(const char* const Tag) const +{ + return Exists(StringView(Tag)); +} + +bool pkgTagSection::Find(const char *Tag,unsigned int &Pos) const +{ + return Find(StringView(Tag), Pos); +} + +bool pkgTagSection::Find(const char *Tag,const char *&Start, + const char *&End) const +{ + return Find(StringView(Tag), Start, End); +} + +string pkgTagSection::FindS(const char *Tag) const +{ + return Find(StringView(Tag)).to_string(); +} + +string pkgTagSection::FindRawS(const char *Tag) const +{ + return FindRaw(StringView(Tag)).to_string(); +} + +signed int pkgTagSection::FindI(const char *Tag,signed long Default) const +{ + return FindI(StringView(Tag), Default); +} + +unsigned long long pkgTagSection::FindULL(const char *Tag, unsigned long long const &Default) const +{ + return FindULL(StringView(Tag), Default); +} + /*}}}*/ + +bool pkgTagSection::FindB(const char *Tag, bool const &Default) const +{ + return FindB(StringView(Tag), Default); +} + +bool pkgTagSection::FindFlag(const char * const Tag, uint8_t &Flags, + uint8_t const Flag) const +{ + return FindFlag(StringView(Tag), Flags, Flag); +} + +bool pkgTagSection::FindFlag(const char *Tag,unsigned long &Flags, + unsigned long Flag) const +{ + return FindFlag(StringView(Tag), Flags, Flag); +} diff --git a/apt-pkg/tagfile-keys.list b/apt-pkg/tagfile-keys.list new file mode 100644 index 0000000..a979a40 --- /dev/null +++ b/apt-pkg/tagfile-keys.list @@ -0,0 +1,83 @@ +Architecture +Binary +Breaks +Bugs +Build-Conflicts +Build-Conflicts-Arch +Build-Conflicts-Indep +Build-Depends +Build-Depends-Arch +Build-Depends-Indep +Build-Essential +Built-For-Profiles +Built-Using +Checksums-Md5 +Checksums-Sha1 +Checksums-Sha256 +Checksums-Sha512 +Class +Conffiles +Config-Version +Conflicts +Depends +Description +Description-md5 +Directory +Dm-Upload-Allowed +Enhances +Essential +Filename +Files +Format +Homepage +Important +Installed-Size +Installer-Menu-Item +Kernel-Version +Maintainer +MD5sum +MSDOS-Filename +Multi-Arch +Optional +Origin +Original-Maintainer +Package +Package-List +Package_Revision +Package-Revision +Package-Type +Pre-Depends +Priority +Provides +Recommended +Recommends +Replaces +Revision +Section +SHA1 +SHA256 +SHA512 +Size +Source +Standards-Version +Status +Subarchitecture +Suggests +Tag +Task +Testsuite +Testsuite-Triggers +Triggers-Awaited +Triggers-Pending +Uploaders +Vcs-Arch +Vcs-Browse +Vcs-Browser +Vcs-Bzr +Vcs-Cvs +Vcs-Darcs +Vcs-Git +Vcs-Hg +Vcs-Mtn +Vcs-Svn +Version diff --git a/apt-pkg/tagfile-order.c b/apt-pkg/tagfile-order.c new file mode 100644 index 0000000..4c2068c --- /dev/null +++ b/apt-pkg/tagfile-order.c @@ -0,0 +1,113 @@ +/* In this file is the order defined in which e.g. apt-ftparchive will write stanzas in. + Other commands might or might not use this. 'apt-cache show' e.g. does NOT! + + The order we chose here is inspired by both dpkg and dak. + The testcase test/integration/test-apt-tagfile-fields-order intends to ensure that + this file isn't lacking (too far) behind dpkg over time. */ + +static const char *iTFRewritePackageOrder[] = { + "Package", + "Package-Type", + "Architecture", + "Subarchitecture", // Used only by d-i + "Version", + "Revision", // Obsolete (warning in dpkg) + "Package-Revision", // Obsolete (warning in dpkg) + "Package_Revision", // Obsolete (warning in dpkg) + "Kernel-Version", // Used only by d-i + "Built-Using", + "Built-For-Profiles", + "Auto-Built-Package", + "Multi-Arch", + "Status", + "Priority", + "Class", // dpkg nickname for Priority + "Build-Essential", + "Essential", + "Installer-Menu-Item", // Used only by d-i + "Section", + "Source", + "Origin", + "Maintainer", + "Original-Maintainer", // unknown in dpkg order + "Bugs", + "Config-Version", // Internal of dpkg + "Conffiles", + "Triggers-Awaited", + "Triggers-Pending", + "Installed-Size", + "Provides", + "Pre-Depends", + "Depends", + "Recommends", + "Recommended", // dpkg nickname for Recommends + "Suggests", + "Optional", // dpkg nickname for Suggests + "Conflicts", + "Breaks", + "Replaces", + "Enhances", + "Filename", + "MSDOS-Filename", // Obsolete (used by dselect) + "Size", + "MD5sum", + "SHA1", + "SHA256", + "SHA512", + "Homepage", + "Description", + "Tag", + "Task", + 0, +}; +static const char *iTFRewriteSourceOrder[] = { + "Package", + "Source", // dsc file, renamed to Package in Sources + "Format", + "Binary", + "Architecture", + "Version", + "Priority", + "Class", // dpkg nickname for Priority + "Section", + "Origin", + "Maintainer", + "Original-Maintainer", // unknown in dpkg order + "Uploaders", + "Dm-Upload-Allowed", // Obsolete (ignored by dak) + "Standards-Version", + "Build-Depends", + "Build-Depends-Arch", + "Build-Depends-Indep", + "Build-Conflicts", + "Build-Conflicts-Arch", + "Build-Conflicts-Indep", + "Testsuite", + "Testsuite-Triggers", + "Homepage", + "Description", + "Vcs-Browser", + "Vcs-Browse", // dak only (nickname?) + "Vcs-Arch", + "Vcs-Bzr", + "Vcs-Cvs", + "Vcs-Darcs", + "Vcs-Git", + "Vcs-Hg", + "Vcs-Mtn", + "Vcs-Svn", + "Directory", + "Package-List", + "Files", + "Checksums-Md5", + "Checksums-Sha1", + "Checksums-Sha256", + "Checksums-Sha512", + 0, +}; + +/* Two levels of initialization are used because gcc will set the symbol + size of an array to the length of the array, causing dynamic relinking + errors. Doing this makes the symbol size constant */ +const char **TFRewritePackageOrder = iTFRewritePackageOrder; +const char **TFRewriteSourceOrder = iTFRewriteSourceOrder; diff --git a/apt-pkg/tagfile.cc b/apt-pkg/tagfile.cc new file mode 100644 index 0000000..1e7f286 --- /dev/null +++ b/apt-pkg/tagfile.cc @@ -0,0 +1,1210 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Fast scanner for RFC-822 type header information + + This uses a rotating buffer to load the package information into. + The scanner runs over it and isolates and indexes a single section. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/string_view.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile-keys.h> +#include <apt-pkg/tagfile.h> + +#include <list> + +#include <string> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <apti18n.h> + /*}}}*/ + +using std::string; +using APT::StringView; + +class APT_HIDDEN pkgTagFilePrivate /*{{{*/ +{ +public: + void Reset(FileFd * const pFd, unsigned long long const pSize, pkgTagFile::Flags const pFlags) + { + if (Buffer != NULL) + free(Buffer); + Buffer = NULL; + Fd = pFd; + Flags = pFlags; + Start = NULL; + End = NULL; + Done = false; + iOffset = 0; + Size = pSize; + isCommentedLine = false; + chunks.clear(); + } + + pkgTagFilePrivate(FileFd * const pFd, unsigned long long const Size, pkgTagFile::Flags const pFlags) : Buffer(NULL) + { + Reset(pFd, Size, pFlags); + } + FileFd * Fd; + pkgTagFile::Flags Flags; + char *Buffer; + char *Start; + char *End; + bool Done; + unsigned long long iOffset; + unsigned long long Size; + bool isCommentedLine; + struct FileChunk + { + bool const good; + size_t length; + FileChunk(bool const pgood, size_t const plength) : good(pgood), length(plength) {} + }; + std::list<FileChunk> chunks; + + ~pkgTagFilePrivate() + { + if (Buffer != NULL) + free(Buffer); + } +}; + /*}}}*/ +class APT_HIDDEN pkgTagSectionPrivate /*{{{*/ +{ +public: + pkgTagSectionPrivate() + { + } + struct TagData { + unsigned int StartTag; + unsigned int EndTag; + unsigned int StartValue; + unsigned int NextInBucket; + + explicit TagData(unsigned int const StartTag) : StartTag(StartTag), EndTag(0), StartValue(0), NextInBucket(0) {} + }; + std::vector<TagData> Tags; +}; + /*}}}*/ + +static unsigned long BetaHash(const char *Text, size_t Length) /*{{{*/ +{ + /* This very simple hash function for the last 8 letters gives + very good performance on the debian package files */ + if (Length > 8) + { + Text += (Length - 8); + Length = 8; + } + unsigned long Res = 0; + for (size_t i = 0; i < Length; ++i) + Res = ((unsigned long)(Text[i]) & 0xDF) ^ (Res << 1); + return Res & 0x7F; +} + /*}}}*/ + +// TagFile::pkgTagFile - Constructor /*{{{*/ +pkgTagFile::pkgTagFile(FileFd * const pFd,pkgTagFile::Flags const pFlags, unsigned long long const Size) + : d(new pkgTagFilePrivate(pFd, Size + 4, pFlags)) +{ + Init(pFd, pFlags, Size); +} +pkgTagFile::pkgTagFile(FileFd * const pFd,unsigned long long const Size) + : pkgTagFile(pFd, pkgTagFile::STRICT, Size) +{ +} +void pkgTagFile::Init(FileFd * const pFd, pkgTagFile::Flags const pFlags, unsigned long long Size) +{ + /* The size is increased by 4 because if we start with the Size of the + filename we need to try to read 1 char more to see an EOF faster, 1 + char the end-pointer can be on and maybe 2 newlines need to be added + to the end of the file -> 4 extra chars */ + Size += 4; + d->Reset(pFd, Size, pFlags); + + if (d->Fd->IsOpen() == false) + d->Start = d->End = d->Buffer = 0; + else + d->Buffer = (char*)malloc(sizeof(char) * Size); + + if (d->Buffer == NULL) + d->Done = true; + else + d->Done = false; + + d->Start = d->End = d->Buffer; + d->iOffset = 0; + if (d->Done == false) + Fill(); +} +void pkgTagFile::Init(FileFd * const pFd,unsigned long long Size) +{ + Init(pFd, pkgTagFile::STRICT, Size); +} + /*}}}*/ +// TagFile::~pkgTagFile - Destructor /*{{{*/ +pkgTagFile::~pkgTagFile() +{ + delete d; +} + /*}}}*/ +// TagFile::Offset - Return the current offset in the buffer /*{{{*/ +APT_PURE unsigned long pkgTagFile::Offset() +{ + return d->iOffset; +} + /*}}}*/ +// TagFile::Resize - Resize the internal buffer /*{{{*/ +// --------------------------------------------------------------------- +/* Resize the internal buffer (double it in size). Fail if a maximum size + * size is reached. + */ +bool pkgTagFile::Resize() +{ + // fail is the buffer grows too big + if(d->Size > 1024*1024+1) + return false; + + return Resize(d->Size * 2); +} +bool pkgTagFile::Resize(unsigned long long const newSize) +{ + unsigned long long const EndSize = d->End - d->Start; + + // get new buffer and use it + char* const newBuffer = static_cast<char*>(realloc(d->Buffer, sizeof(char) * newSize)); + if (newBuffer == NULL) + return false; + d->Buffer = newBuffer; + d->Size = newSize; + + // update the start/end pointers to the new buffer + d->Start = d->Buffer; + d->End = d->Start + EndSize; + return true; +} + /*}}}*/ +// TagFile::Step - Advance to the next section /*{{{*/ +// --------------------------------------------------------------------- +/* If the Section Scanner fails we refill the buffer and try again. + * If that fails too, double the buffer size and try again until a + * maximum buffer is reached. + */ +bool pkgTagFile::Step(pkgTagSection &Tag) +{ + if(Tag.Scan(d->Start,d->End - d->Start) == false) + { + do + { + if (Fill() == false) + return false; + + if(Tag.Scan(d->Start,d->End - d->Start, false)) + break; + + if (Resize() == false) + return _error->Error(_("Unable to parse package file %s (%d)"), + d->Fd->Name().c_str(), 1); + + } while (Tag.Scan(d->Start,d->End - d->Start, false) == false); + } + + size_t tagSize = Tag.size(); + d->Start += tagSize; + + if ((d->Flags & pkgTagFile::SUPPORT_COMMENTS) == 0) + d->iOffset += tagSize; + else + { + auto first = d->chunks.begin(); + for (; first != d->chunks.end(); ++first) + { + if (first->good == false) + d->iOffset += first->length; + else + { + if (tagSize < first->length) + { + first->length -= tagSize; + d->iOffset += tagSize; + break; + } + else + { + tagSize -= first->length; + d->iOffset += first->length; + } + } + } + d->chunks.erase(d->chunks.begin(), first); + } + + if ((d->Flags & pkgTagFile::SUPPORT_COMMENTS) == 0 || Tag.Count() != 0) + { + Tag.Trim(); + return true; + } + return Step(Tag); +} + /*}}}*/ +// TagFile::Fill - Top up the buffer /*{{{*/ +// --------------------------------------------------------------------- +/* This takes the bit at the end of the buffer and puts it at the start + then fills the rest from the file */ +static bool FillBuffer(pkgTagFilePrivate * const d) +{ + unsigned long long Actual = 0; + // See if only a bit of the file is left + unsigned long long const dataSize = d->Size - ((d->End - d->Buffer) + 1); + if (d->Fd->Read(d->End, dataSize, &Actual) == false) + return false; + if (Actual != dataSize) + d->Done = true; + d->End += Actual; + return true; +} +static void RemoveCommentsFromBuffer(pkgTagFilePrivate * const d) +{ + // look for valid comments in the buffer + char * good_start = nullptr, * bad_start = nullptr; + char * current = d->Start; + if (d->isCommentedLine == false) + { + if (d->Start == d->Buffer) + { + // the start of the buffer is a newline as a record can't start + // in the middle of a line by definition. + if (*d->Start == '#') + { + d->isCommentedLine = true; + ++current; + if (current > d->End) + d->chunks.emplace_back(false, 1); + } + } + if (d->isCommentedLine == false) + good_start = d->Start; + else + bad_start = d->Start; + } + else + bad_start = d->Start; + + std::vector<std::pair<char*, size_t>> good_parts; + while (current <= d->End) + { + size_t const restLength = (d->End - current); + if (d->isCommentedLine == false) + { + current = static_cast<char*>(memchr(current, '#', restLength)); + if (current == nullptr) + { + size_t const goodLength = d->End - good_start; + d->chunks.emplace_back(true, goodLength); + if (good_start != d->Start) + good_parts.push_back(std::make_pair(good_start, goodLength)); + break; + } + bad_start = current; + --current; + // ensure that this is really a comment and not a '#' in the middle of a line + if (*current == '\n') + { + size_t const goodLength = (current - good_start) + 1; + d->chunks.emplace_back(true, goodLength); + good_parts.push_back(std::make_pair(good_start, goodLength)); + good_start = nullptr; + d->isCommentedLine = true; + } + current += 2; + } + else // the current line is a comment + { + current = static_cast<char*>(memchr(current, '\n', restLength)); + if (current == nullptr) + { + d->chunks.emplace_back(false, (d->End - bad_start)); + break; + } + ++current; + // is the next line a comment, too? + if (current >= d->End || *current != '#') + { + d->chunks.emplace_back(false, (current - bad_start)); + good_start = current; + bad_start = nullptr; + d->isCommentedLine = false; + } + ++current; + } + } + + if (good_parts.empty() == false) + { + // we found comments, so move later parts over them + current = d->Start; + for (auto const &good: good_parts) + { + memmove(current, good.first, good.second); + current += good.second; + } + d->End = current; + } + + if (d->isCommentedLine == true) + { + // deal with a buffer containing only comments + // or an (unfinished) comment at the end + if (good_parts.empty() == true) + d->End = d->Start; + else + d->Start = d->End; + } + else + { + // the buffer was all comment, but ended with the buffer + if (good_parts.empty() == true && good_start >= d->End) + d->End = d->Start; + else + d->Start = d->End; + } +} +bool pkgTagFile::Fill() +{ + unsigned long long const EndSize = d->End - d->Start; + if (EndSize != 0) + { + memmove(d->Buffer,d->Start,EndSize); + d->Start = d->End = d->Buffer + EndSize; + } + else + d->Start = d->End = d->Buffer; + + unsigned long long Actual = 0; + while (d->Done == false && d->Size > (Actual + 1)) + { + if (FillBuffer(d) == false) + return false; + if ((d->Flags & pkgTagFile::SUPPORT_COMMENTS) != 0) + RemoveCommentsFromBuffer(d); + Actual = d->End - d->Buffer; + } + d->Start = d->Buffer; + + if (d->Done == true) + { + if (EndSize <= 3 && Actual == 0) + return false; + if (d->Size - (d->End - d->Buffer) < 4) + return true; + + // Append a double new line if one does not exist + unsigned int LineCount = 0; + for (const char *E = d->End - 1; E - d->End < 6 && (*E == '\n' || *E == '\r'); E--) + if (*E == '\n') + ++LineCount; + if (LineCount < 2) + { + if (static_cast<unsigned long long>(d->End - d->Buffer) >= d->Size) + Resize(d->Size + 3); + for (; LineCount < 2; ++LineCount) + *d->End++ = '\n'; + } + } + return true; +} + /*}}}*/ +// TagFile::Jump - Jump to a pre-recorded location in the file /*{{{*/ +// --------------------------------------------------------------------- +/* This jumps to a pre-recorded file location and reads the record + that is there */ +bool pkgTagFile::Jump(pkgTagSection &Tag,unsigned long long Offset) +{ + if ((d->Flags & pkgTagFile::SUPPORT_COMMENTS) == 0 && + // We are within a buffer space of the next hit.. + Offset >= d->iOffset && d->iOffset + (d->End - d->Start) > Offset) + { + unsigned long long Dist = Offset - d->iOffset; + d->Start += Dist; + d->iOffset += Dist; + // if we have seen the end, don't ask for more + if (d->Done == true) + return Tag.Scan(d->Start, d->End - d->Start); + else + return Step(Tag); + } + + // Reposition and reload.. + d->iOffset = Offset; + d->Done = false; + if (d->Fd->Seek(Offset) == false) + return false; + d->End = d->Start = d->Buffer; + d->isCommentedLine = false; + d->chunks.clear(); + + if (Fill() == false) + return false; + + if (Tag.Scan(d->Start, d->End - d->Start) == true) + return true; + + // This appends a double new line (for the real eof handling) + if (Fill() == false) + return false; + + if (Tag.Scan(d->Start, d->End - d->Start, false) == false) + return _error->Error(_("Unable to parse package file %s (%d)"),d->Fd->Name().c_str(), 2); + + return true; +} + /*}}}*/ +// pkgTagSection::pkgTagSection - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* */ +APT_IGNORE_DEPRECATED_PUSH +pkgTagSection::pkgTagSection() + : Section(0), d(new pkgTagSectionPrivate()), Stop(0) +{ + memset(&AlphaIndexes, 0, sizeof(AlphaIndexes)); + memset(&BetaIndexes, 0, sizeof(BetaIndexes)); +} +APT_IGNORE_DEPRECATED_POP + /*}}}*/ +// TagSection::Scan - Scan for the end of the header information /*{{{*/ +bool pkgTagSection::Scan(const char *Start,unsigned long MaxLength, bool const Restart) +{ + Section = Start; + const char *End = Start + MaxLength; + + if (Restart == false && d->Tags.empty() == false) + { + Stop = Section + d->Tags.back().StartTag; + if (End <= Stop) + return false; + Stop = (const char *)memchr(Stop,'\n',End - Stop); + if (Stop == NULL) + return false; + ++Stop; + } + else + { + Stop = Section; + if (d->Tags.empty() == false) + { + memset(&AlphaIndexes, 0, sizeof(AlphaIndexes)); + memset(&BetaIndexes, 0, sizeof(BetaIndexes)); + d->Tags.clear(); + } + d->Tags.reserve(0x100); + } + unsigned int TagCount = d->Tags.size(); + + if (Stop == 0) + return false; + + pkgTagSectionPrivate::TagData lastTagData(0); + lastTagData.EndTag = 0; + Key lastTagKey = Key::Unknown; + unsigned int lastTagHash = 0; + while (Stop < End) + { + TrimRecord(true,End); + + // this can happen when TrimRecord trims away the entire Record + // (e.g. because it just contains comments) + if(Stop == End) + return true; + + // Start a new index and add it to the hash + if (isspace_ascii(Stop[0]) == 0) + { + // store the last found tag + if (lastTagData.EndTag != 0) + { + if (lastTagKey != Key::Unknown) { + AlphaIndexes[static_cast<size_t>(lastTagKey)] = TagCount; + } else { + if (BetaIndexes[lastTagHash] != 0) + lastTagData.NextInBucket = BetaIndexes[lastTagHash]; + APT_IGNORE_DEPRECATED_PUSH + BetaIndexes[lastTagHash] = TagCount; + APT_IGNORE_DEPRECATED_POP + } + d->Tags.push_back(lastTagData); + } + + APT_IGNORE_DEPRECATED(++TagCount;) + lastTagData = pkgTagSectionPrivate::TagData(Stop - Section); + // find the colon separating tag and value + char const * Colon = (char const *) memchr(Stop, ':', End - Stop); + if (Colon == NULL) + return false; + // find the end of the tag (which might or might not be the colon) + char const * EndTag = Colon; + --EndTag; + for (; EndTag > Stop && isspace_ascii(*EndTag) != 0; --EndTag) + ; + ++EndTag; + lastTagData.EndTag = EndTag - Section; + lastTagKey = pkgTagHash(Stop, EndTag - Stop); + if (lastTagKey == Key::Unknown) + lastTagHash = BetaHash(Stop, EndTag - Stop); + // find the beginning of the value + Stop = Colon + 1; + for (; Stop < End && isspace_ascii(*Stop) != 0; ++Stop) + if (*Stop == '\n' && Stop[1] != ' ') + break; + if (Stop >= End) + return false; + lastTagData.StartValue = Stop - Section; + } + + Stop = (const char *)memchr(Stop,'\n',End - Stop); + + if (Stop == 0) + return false; + + for (; Stop+1 < End && Stop[1] == '\r'; Stop++) + /* nothing */ + ; + + // Double newline marks the end of the record + if (Stop+1 < End && Stop[1] == '\n') + { + if (lastTagData.EndTag != 0) + { + if (lastTagKey != Key::Unknown) { + AlphaIndexes[static_cast<size_t>(lastTagKey)] = TagCount; + } else { + if (BetaIndexes[lastTagHash] != 0) + lastTagData.NextInBucket = BetaIndexes[lastTagHash]; + APT_IGNORE_DEPRECATED(BetaIndexes[lastTagHash] = TagCount;) + } + d->Tags.push_back(lastTagData); + } + + pkgTagSectionPrivate::TagData const td(Stop - Section); + d->Tags.push_back(td); + TrimRecord(false,End); + return true; + } + + Stop++; + } + + return false; +} + /*}}}*/ +// TagSection::TrimRecord - Trim off any garbage before/after a record /*{{{*/ +// --------------------------------------------------------------------- +/* There should be exactly 2 newline at the end of the record, no more. */ +void pkgTagSection::TrimRecord(bool BeforeRecord, const char*& End) +{ + if (BeforeRecord == true) + return; + for (; Stop < End && (Stop[0] == '\n' || Stop[0] == '\r'); Stop++); +} + /*}}}*/ +// TagSection::Trim - Trim off any trailing garbage /*{{{*/ +// --------------------------------------------------------------------- +/* There should be exactly 1 newline at the end of the buffer, no more. */ +void pkgTagSection::Trim() +{ + for (; Stop > Section + 2 && (Stop[-2] == '\n' || Stop[-2] == '\r'); Stop--); +} + /*}}}*/ +// TagSection::Exists - return True if a tag exists /*{{{*/ +bool pkgTagSection::Exists(StringView Tag) const +{ + unsigned int tmp; + return Find(Tag, tmp); +} + /*}}}*/ +// TagSection::Find - Locate a tag /*{{{*/ +// --------------------------------------------------------------------- +/* This searches the section for a tag that matches the given string. */ +bool pkgTagSection::Find(Key key,unsigned int &Pos) const +{ + auto Bucket = AlphaIndexes[static_cast<size_t>(key)]; + Pos = Bucket - 1; + return Bucket != 0; +} +bool pkgTagSection::Find(StringView TagView,unsigned int &Pos) const +{ + const char * const Tag = TagView.data(); + size_t const Length = TagView.length(); + auto key = pkgTagHash(Tag, Length); + if (key != Key::Unknown) + return Find(key, Pos); + + unsigned int Bucket = BetaIndexes[BetaHash(Tag, Length)]; + if (Bucket == 0) + return false; + + for (; Bucket != 0; Bucket = d->Tags[Bucket - 1].NextInBucket) + { + if ((d->Tags[Bucket - 1].EndTag - d->Tags[Bucket - 1].StartTag) != Length) + continue; + + char const * const St = Section + d->Tags[Bucket - 1].StartTag; + if (strncasecmp(Tag,St,Length) != 0) + continue; + + Pos = Bucket - 1; + return true; + } + + Pos = 0; + return false; +} + +bool pkgTagSection::FindInternal(unsigned int Pos, const char *&Start, + const char *&End) const +{ + Start = Section + d->Tags[Pos].StartValue; + // Strip off the gunk from the end + End = Section + d->Tags[Pos + 1].StartTag; + if (unlikely(Start > End)) + return _error->Error("Internal parsing error"); + + for (; isspace_ascii(End[-1]) != 0 && End > Start; --End); + + return true; +} +bool pkgTagSection::Find(StringView Tag,const char *&Start, + const char *&End) const +{ + unsigned int Pos; + return Find(Tag, Pos) && FindInternal(Pos, Start, End); +} +bool pkgTagSection::Find(Key key,const char *&Start, + const char *&End) const +{ + unsigned int Pos; + return Find(key, Pos) && FindInternal(Pos, Start, End); +} + /*}}}*/ +// TagSection::FindS - Find a string /*{{{*/ +StringView pkgTagSection::Find(StringView Tag) const +{ + const char *Start; + const char *End; + if (Find(Tag,Start,End) == false) + return StringView(); + return StringView(Start, End - Start); +} +StringView pkgTagSection::Find(Key key) const +{ + const char *Start; + const char *End; + if (Find(key,Start,End) == false) + return StringView(); + return StringView(Start, End - Start); +} + /*}}}*/ +// TagSection::FindRawS - Find a string /*{{{*/ +StringView pkgTagSection::FindRawInternal(unsigned int Pos) const +{ + char const *Start = (char const *) memchr(Section + d->Tags[Pos].EndTag, ':', d->Tags[Pos].StartValue - d->Tags[Pos].EndTag); + ++Start; + char const *End = Section + d->Tags[Pos + 1].StartTag; + if (unlikely(Start > End)) + return ""; + + for (; isspace_ascii(End[-1]) != 0 && End > Start; --End); + + return StringView(Start, End - Start); +} +StringView pkgTagSection::FindRaw(StringView Tag) const +{ + unsigned int Pos; + return Find(Tag, Pos) ? FindRawInternal(Pos) : ""; +} +StringView pkgTagSection::FindRaw(Key key) const +{ + unsigned int Pos; + return Find(key, Pos) ? FindRawInternal(Pos) : ""; +} + /*}}}*/ +// TagSection::FindI - Find an integer /*{{{*/ +// --------------------------------------------------------------------- +/* */ +signed int pkgTagSection::FindIInternal(unsigned int Pos,signed long Default) const +{ + const char *Start; + const char *Stop; + if (FindInternal(Pos,Start,Stop) == false) + return Default; + + // Copy it into a temp buffer so we can use strtol + char S[300]; + if ((unsigned)(Stop - Start) >= sizeof(S)) + return Default; + strncpy(S,Start,Stop-Start); + S[Stop - Start] = 0; + + errno = 0; + char *End; + signed long Result = strtol(S,&End,10); + if (errno == ERANGE || + Result < std::numeric_limits<int>::min() || Result > std::numeric_limits<int>::max()) { + errno = ERANGE; + _error->Error(_("Cannot convert %s to integer: out of range"), S); + } + if (S == End) + return Default; + return Result; +} +signed int pkgTagSection::FindI(Key key,signed long Default) const +{ + unsigned int Pos; + + return Find(key, Pos) ? FindIInternal(Pos) : Default; +} +signed int pkgTagSection::FindI(StringView Tag,signed long Default) const +{ + unsigned int Pos; + + return Find(Tag, Pos) ? FindIInternal(Pos, Default) : Default; +} + /*}}}*/ +// TagSection::FindULL - Find an unsigned long long integer /*{{{*/ +// --------------------------------------------------------------------- +/* */ +unsigned long long pkgTagSection::FindULLInternal(unsigned int Pos, unsigned long long const &Default) const +{ + const char *Start; + const char *Stop; + if (FindInternal(Pos,Start,Stop) == false) + return Default; + + // Copy it into a temp buffer so we can use strtoull + char S[100]; + if ((unsigned)(Stop - Start) >= sizeof(S)) + return Default; + strncpy(S,Start,Stop-Start); + S[Stop - Start] = 0; + + char *End; + unsigned long long Result = strtoull(S,&End,10); + if (S == End) + return Default; + return Result; +} +unsigned long long pkgTagSection::FindULL(Key key, unsigned long long const &Default) const +{ + unsigned int Pos; + + return Find(key, Pos) ? FindULLInternal(Pos, Default) : Default; +} +unsigned long long pkgTagSection::FindULL(StringView Tag, unsigned long long const &Default) const +{ + unsigned int Pos; + + return Find(Tag, Pos) ? FindULLInternal(Pos, Default) : Default; +} + /*}}}*/ +// TagSection::FindB - Find boolean value /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgTagSection::FindBInternal(unsigned int Pos, bool Default) const +{ + const char *Start, *Stop; + if (FindInternal(Pos, Start, Stop) == false) + return Default; + return StringToBool(string(Start, Stop)); +} +bool pkgTagSection::FindB(Key key, bool Default) const +{ + unsigned int Pos; + return Find(key, Pos) ? FindBInternal(Pos, Default): Default; +} +bool pkgTagSection::FindB(StringView Tag, bool Default) const +{ + unsigned int Pos; + return Find(Tag, Pos) ? FindBInternal(Pos, Default) : Default; +} + /*}}}*/ +// TagSection::FindFlag - Locate a yes/no type flag /*{{{*/ +// --------------------------------------------------------------------- +/* The bits marked in Flag are masked on/off in Flags */ +bool pkgTagSection::FindFlagInternal(unsigned int Pos, uint8_t &Flags, + uint8_t const Flag) const +{ + const char *Start; + const char *Stop; + if (FindInternal(Pos,Start,Stop) == false) + return true; + return FindFlag(Flags, Flag, Start, Stop); +} +bool pkgTagSection::FindFlag(Key key, uint8_t &Flags, + uint8_t const Flag) const +{ + unsigned int Pos; + if (Find(key,Pos) == false) + return true; + return FindFlagInternal(Pos, Flags, Flag); +} +bool pkgTagSection::FindFlag(StringView Tag, uint8_t &Flags, + uint8_t const Flag) const +{ + unsigned int Pos; + if (Find(Tag,Pos) == false) + return true; + return FindFlagInternal(Pos, Flags, Flag); +} +bool pkgTagSection::FindFlag(uint8_t &Flags, uint8_t const Flag, + char const* const Start, char const* const Stop) +{ + switch (StringToBool(string(Start, Stop))) + { + case 0: + Flags &= ~Flag; + return true; + + case 1: + Flags |= Flag; + return true; + + default: + _error->Warning("Unknown flag value: %s",string(Start,Stop).c_str()); + return true; + } + return true; +} +bool pkgTagSection::FindFlagInternal(unsigned int Pos,unsigned long &Flags, + unsigned long Flag) const +{ + const char *Start; + const char *Stop; + if (FindInternal(Pos,Start,Stop) == false) + return true; + return FindFlag(Flags, Flag, Start, Stop); +} +bool pkgTagSection::FindFlag(Key key,unsigned long &Flags, + unsigned long Flag) const +{ + unsigned int Pos; + return Find(key, Pos) ? FindFlagInternal(Pos, Flags, Flag) : true; +} +bool pkgTagSection::FindFlag(StringView Tag,unsigned long &Flags, + unsigned long Flag) const +{ + unsigned int Pos; + return Find(Tag, Pos) ? FindFlagInternal(Pos, Flags, Flag) : true; +} +bool pkgTagSection::FindFlag(unsigned long &Flags, unsigned long Flag, + char const* Start, char const* Stop) +{ + switch (StringToBool(string(Start, Stop))) + { + case 0: + Flags &= ~Flag; + return true; + + case 1: + Flags |= Flag; + return true; + + default: + _error->Warning("Unknown flag value: %s",string(Start,Stop).c_str()); + return true; + } + return true; +} + /*}}}*/ +void pkgTagSection::Get(const char *&Start,const char *&Stop,unsigned int I) const/*{{{*/ +{ + Start = Section + d->Tags[I].StartTag; + Stop = Section + d->Tags[I+1].StartTag; +} + /*}}}*/ +APT_PURE unsigned int pkgTagSection::Count() const { /*{{{*/ + if (d->Tags.empty() == true) + return 0; + // the last element is just marking the end and isn't a real one + return d->Tags.size() - 1; +} + /*}}}*/ +// TagSection::Write - Ordered (re)writing of fields /*{{{*/ +pkgTagSection::Tag pkgTagSection::Tag::Remove(std::string const &Name) +{ + return Tag(REMOVE, Name, ""); +} +pkgTagSection::Tag pkgTagSection::Tag::Rename(std::string const &OldName, std::string const &NewName) +{ + return Tag(RENAME, OldName, NewName); +} +pkgTagSection::Tag pkgTagSection::Tag::Rewrite(std::string const &Name, std::string const &Data) +{ + if (Data.empty() == true) + return Tag(REMOVE, Name, ""); + else + return Tag(REWRITE, Name, Data); +} +static bool WriteTag(FileFd &File, std::string Tag, StringView Value) +{ + if (Value.empty() || isspace_ascii(Value[0]) != 0) + Tag.append(":"); + else + Tag.append(": "); + Tag.append(Value.data(), Value.length()); + Tag.append("\n"); + return File.Write(Tag.c_str(), Tag.length()); +} +static bool RewriteTags(FileFd &File, pkgTagSection const * const This, char const * const Tag, + std::vector<pkgTagSection::Tag>::const_iterator &R, + std::vector<pkgTagSection::Tag>::const_iterator const &REnd) +{ + size_t const TagLen = strlen(Tag); + for (; R != REnd; ++R) + { + std::string data; + if (R->Name.length() == TagLen && strncasecmp(R->Name.c_str(), Tag, R->Name.length()) == 0) + { + if (R->Action != pkgTagSection::Tag::REWRITE) + break; + data = R->Data; + } + else if(R->Action == pkgTagSection::Tag::RENAME && R->Data.length() == TagLen && + strncasecmp(R->Data.c_str(), Tag, R->Data.length()) == 0) + data = This->FindRaw(R->Name.c_str()).to_string(); + else + continue; + + return WriteTag(File, Tag, data); + } + return true; +} +bool pkgTagSection::Write(FileFd &File, char const * const * const Order, std::vector<Tag> const &Rewrite) const +{ + // first pass: Write everything we have an order for + if (Order != NULL) + { + for (unsigned int I = 0; Order[I] != 0; ++I) + { + std::vector<Tag>::const_iterator R = Rewrite.begin(); + if (RewriteTags(File, this, Order[I], R, Rewrite.end()) == false) + return false; + if (R != Rewrite.end()) + continue; + + if (Exists(Order[I]) == false) + continue; + + if (WriteTag(File, Order[I], FindRaw(Order[I])) == false) + return false; + } + } + // second pass: See if we have tags which aren't ordered + if (d->Tags.empty() == false) + { + for (std::vector<pkgTagSectionPrivate::TagData>::const_iterator T = d->Tags.begin(); T != d->Tags.end() - 1; ++T) + { + char const * const fieldname = Section + T->StartTag; + size_t fieldnamelen = T->EndTag - T->StartTag; + if (Order != NULL) + { + unsigned int I = 0; + for (; Order[I] != 0; ++I) + { + if (fieldnamelen == strlen(Order[I]) && strncasecmp(fieldname, Order[I], fieldnamelen) == 0) + break; + } + if (Order[I] != 0) + continue; + } + + std::string const name(fieldname, fieldnamelen); + std::vector<Tag>::const_iterator R = Rewrite.begin(); + if (RewriteTags(File, this, name.c_str(), R, Rewrite.end()) == false) + return false; + if (R != Rewrite.end()) + continue; + + if (WriteTag(File, name, FindRaw(name)) == false) + return false; + } + } + // last pass: see if there are any rewrites remaining we haven't done yet + for (std::vector<Tag>::const_iterator R = Rewrite.begin(); R != Rewrite.end(); ++R) + { + if (R->Action == Tag::REMOVE) + continue; + std::string const name = ((R->Action == Tag::RENAME) ? R->Data : R->Name); + if (Exists(name.c_str())) + continue; + if (Order != NULL) + { + unsigned int I = 0; + for (; Order[I] != 0; ++I) + { + if (strncasecmp(name.c_str(), Order[I], name.length()) == 0 && name.length() == strlen(Order[I])) + break; + } + if (Order[I] != 0) + continue; + } + + if (WriteTag(File, name, ((R->Action == Tag::RENAME) ? FindRaw(R->Name) : R->Data)) == false) + return false; + } + return true; +} + /*}}}*/ + +void pkgUserTagSection::TrimRecord(bool /*BeforeRecord*/, const char* &End)/*{{{*/ +{ + for (; Stop < End && (Stop[0] == '\n' || Stop[0] == '\r' || Stop[0] == '#'); Stop++) + if (Stop[0] == '#') + Stop = (const char*) memchr(Stop,'\n',End-Stop); +} + /*}}}*/ + +#include "tagfile-order.c" + +// TFRewrite - Rewrite a control record /*{{{*/ +// --------------------------------------------------------------------- +/* This writes the control record to stdout rewriting it as necessary. The + override map item specificies the rewriting rules to follow. This also + takes the time to sort the feild list. */ +APT_IGNORE_DEPRECATED_PUSH +bool TFRewrite(FILE *Output,pkgTagSection const &Tags,const char *Order[], + TFRewriteData *Rewrite) +{ + unsigned char Visited[256]; // Bit 1 is Order, Bit 2 is Rewrite + for (unsigned I = 0; I != 256; I++) + Visited[I] = 0; + + // Set new tag up as necessary. + for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++) + { + if (Rewrite[J].NewTag == 0) + Rewrite[J].NewTag = Rewrite[J].Tag; + } + + // Write all of the tags, in order. + if (Order != NULL) + { + for (unsigned int I = 0; Order[I] != 0; I++) + { + bool Rewritten = false; + + // See if this is a field that needs to be rewritten + for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++) + { + if (strcasecmp(Rewrite[J].Tag,Order[I]) == 0) + { + Visited[J] |= 2; + if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0) + { + if (isspace_ascii(Rewrite[J].Rewrite[0])) + fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite); + else + fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite); + } + Rewritten = true; + break; + } + } + + // See if it is in the fragment + unsigned Pos; + if (Tags.Find(StringView(Order[I]),Pos) == false) + continue; + Visited[Pos] |= 1; + + if (Rewritten == true) + continue; + + /* Write out this element, taking a moment to rewrite the tag + in case of changes of case. */ + const char *Start; + const char *Stop; + Tags.Get(Start,Stop,Pos); + + if (fputs(Order[I],Output) < 0) + return _error->Errno("fputs","IO Error to output"); + Start += strlen(Order[I]); + if (fwrite(Start,Stop - Start,1,Output) != 1) + return _error->Errno("fwrite","IO Error to output"); + if (Stop[-1] != '\n') + fprintf(Output,"\n"); + } + } + + // Now write all the old tags that were missed. + for (unsigned int I = 0; I != Tags.Count(); I++) + { + if ((Visited[I] & 1) == 1) + continue; + + const char *Start; + const char *Stop; + Tags.Get(Start,Stop,I); + const char *End = Start; + for (; End < Stop && *End != ':'; End++); + + // See if this is a field that needs to be rewritten + bool Rewritten = false; + for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++) + { + if (stringcasecmp(Start,End,Rewrite[J].Tag) == 0) + { + Visited[J] |= 2; + if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0) + { + if (isspace_ascii(Rewrite[J].Rewrite[0])) + fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite); + else + fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite); + } + + Rewritten = true; + break; + } + } + + if (Rewritten == true) + continue; + + // Write out this element + if (fwrite(Start,Stop - Start,1,Output) != 1) + return _error->Errno("fwrite","IO Error to output"); + if (Stop[-1] != '\n') + fprintf(Output,"\n"); + } + + // Now write all the rewrites that were missed + for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++) + { + if ((Visited[J] & 2) == 2) + continue; + + if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0) + { + if (isspace_ascii(Rewrite[J].Rewrite[0])) + fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite); + else + fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite); + } + } + + return true; +} +APT_IGNORE_DEPRECATED_POP + /*}}}*/ + +pkgTagSection::~pkgTagSection() { delete d; } diff --git a/apt-pkg/tagfile.h b/apt-pkg/tagfile.h new file mode 100644 index 0000000..8b59c43 --- /dev/null +++ b/apt-pkg/tagfile.h @@ -0,0 +1,241 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Fast scanner for RFC-822 type header information + + This parser handles Debian package files (and others). Their form is + RFC-822 type header fields in groups separated by a blank line. + + The parser reads the file and provides methods to step linearly + over it or to jump to a pre-recorded start point and read that record. + + A second class is used to perform pre-parsing of the record. It works + by indexing the start of each header field and providing lookup + functions for header fields. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_TAGFILE_H +#define PKGLIB_TAGFILE_H + +#include <apt-pkg/macros.h> + +#include <stdint.h> +#include <stdio.h> + +#include <list> +#include <string> +#include <vector> +#ifdef APT_PKG_EXPOSE_STRING_VIEW +#include <apt-pkg/string_view.h> +#endif + +#ifndef APT_8_CLEANER_HEADERS +#include <apt-pkg/fileutl.h> +#endif + +class FileFd; +class pkgTagSectionPrivate; +class pkgTagFilePrivate; + +/** \class pkgTagSection parses a single deb822 stanza and provides various Find methods + * to extract the included values. It can also be used to modify and write a + * valid deb822 stanza optionally (re)ordering the fields inside the stanza. + * + * Beware: This class does \b NOT support (#-)comments in in- or output! + * If the input contains comments they have to be stripped first like pkgTagFile + * does with SUPPORT_COMMENTS flag set. */ +class pkgTagSection +{ + const char *Section; + unsigned int AlphaIndexes[128]; + unsigned int BetaIndexes[128]; + + pkgTagSectionPrivate * const d; + + APT_HIDDEN bool FindInternal(unsigned int Pos,const char *&Start, const char *&End) const; +#if defined(APT_PKG_EXPOSE_STRING_VIEW) + APT_HIDDEN APT::StringView FindInternal(unsigned int Pos) const; + APT_HIDDEN APT::StringView FindRawInternal(unsigned int Pos) const; +#endif + APT_HIDDEN signed int FindIInternal(unsigned int Pos,signed long Default = 0) const; + APT_HIDDEN bool FindBInternal(unsigned int Pos, bool Default = false) const; + APT_HIDDEN unsigned long long FindULLInternal(unsigned int Pos, unsigned long long const &Default = 0) const; + APT_HIDDEN bool FindFlagInternal(unsigned int Pos,uint8_t &Flags, uint8_t const Flag) const; + APT_HIDDEN bool FindFlagInternal(unsigned int Pos,unsigned long &Flags, unsigned long Flag) const; + + protected: + const char *Stop; + + public: + + inline bool operator ==(const pkgTagSection &rhs) {return Section == rhs.Section;}; + inline bool operator !=(const pkgTagSection &rhs) {return Section != rhs.Section;}; + +#if !defined(APT_PKG_EXPOSE_STRING_VIEW) || defined(APT_COMPILING_TAGFILE_COMPAT_CC) + bool Find(const char *Tag,const char *&Start, const char *&End) const; + bool Find(const char *Tag,unsigned int &Pos) const; + signed int FindI(const char *Tag,signed long Default = 0) const; + bool FindB(const char *Tag, bool const &Default = false) const; + unsigned long long FindULL(const char *Tag, unsigned long long const &Default = 0) const; + bool FindFlag(const char * const Tag,uint8_t &Flags, + uint8_t const Flag) const; + bool FindFlag(const char *Tag,unsigned long &Flags, + unsigned long Flag) const; + bool Exists(const char* const Tag) const; +#endif + // TODO: Remove internally + std::string FindS(const char *Tag) const; + std::string FindRawS(const char *Tag) const; + + // Functions for lookup with a perfect hash function + enum class Key; + APT_HIDDEN bool Find(Key key,const char *&Start, const char *&End) const; + APT_HIDDEN bool Find(Key key,unsigned int &Pos) const; + APT_HIDDEN signed int FindI(Key key,signed long Default = 0) const; + APT_HIDDEN bool FindB(Key key, bool Default = false) const; + APT_HIDDEN unsigned long long FindULL(Key key, unsigned long long const &Default = 0) const; + APT_HIDDEN bool FindFlag(Key key,uint8_t &Flags, uint8_t const Flag) const; + APT_HIDDEN bool FindFlag(Key key,unsigned long &Flags, unsigned long Flag) const; + APT_HIDDEN bool Exists(Key key) const; +#ifdef APT_PKG_EXPOSE_STRING_VIEW + APT_HIDDEN APT::StringView Find(Key key) const; + APT_HIDDEN APT::StringView FindRaw(Key key) const; + APT_HIDDEN bool Find(APT::StringView Tag,const char *&Start, const char *&End) const; + APT_HIDDEN bool Find(APT::StringView Tag,unsigned int &Pos) const; + APT_HIDDEN APT::StringView Find(APT::StringView Tag) const; + APT_HIDDEN APT::StringView FindRaw(APT::StringView Tag) const; + APT_HIDDEN signed int FindI(APT::StringView Tag,signed long Default = 0) const; + APT_HIDDEN bool FindB(APT::StringView, bool Default = false) const; + APT_HIDDEN unsigned long long FindULL(APT::StringView Tag, unsigned long long const &Default = 0) const; + + APT_HIDDEN bool FindFlag(APT::StringView Tag,uint8_t &Flags, + uint8_t const Flag) const; + APT_HIDDEN bool FindFlag(APT::StringView Tag,unsigned long &Flags, + unsigned long Flag) const; + APT_HIDDEN bool Exists(APT::StringView Tag) const; +#endif + + bool static FindFlag(uint8_t &Flags, uint8_t const Flag, + const char* const Start, const char* const Stop); + bool static FindFlag(unsigned long &Flags, unsigned long Flag, + const char* Start, const char* Stop); + + /** \brief searches the boundaries of the current section + * + * While parameter Start marks the beginning of the section, this method + * will search for the first double newline in the data stream which marks + * the end of the section. It also does a first pass over the content of + * the section parsing it as encountered for processing later on by Find + * + * @param Start is the beginning of the section + * @param MaxLength is the size of valid data in the stream pointed to by Start + * @param Restart if enabled internal state will be cleared, otherwise it is + * assumed that now more data is available in the stream and the parsing will + * start were it encountered insufficient data the last time. + * + * @return \b true if section end was found, \b false otherwise. + * Beware that internal state will be inconsistent if \b false is returned! + */ + APT_MUSTCHECK bool Scan(const char *Start, unsigned long MaxLength, bool const Restart = true); + + inline unsigned long size() const {return Stop - Section;}; + void Trim(); + virtual void TrimRecord(bool BeforeRecord, const char* &End); + + /** \brief amount of Tags in the current section + * + * Note: if a Tag is mentioned repeatedly it will be counted multiple + * times, but only the last occurrence is available via Find methods. + */ + unsigned int Count() const; + + void Get(const char *&Start,const char *&Stop,unsigned int I) const; + + inline void GetSection(const char *&Start,const char *&Stop) const + { + Start = Section; + Stop = this->Stop; + }; + + pkgTagSection(); + virtual ~pkgTagSection(); + + struct Tag + { + enum ActionType { REMOVE, RENAME, REWRITE } Action; + std::string Name; + std::string Data; + + static Tag Remove(std::string const &Name); + static Tag Rename(std::string const &OldName, std::string const &NewName); + static Tag Rewrite(std::string const &Name, std::string const &Data); + private: + Tag(ActionType const Action, std::string const &Name, std::string const &Data) : + Action(Action), Name(Name), Data(Data) {} + }; + + /** Write this section (with optional rewrites) to a file + * + * @param File to write the section to + * @param Order in which tags should appear in the file + * @param Rewrite is a set of tags to be renamed, rewritten and/or removed + * @return \b true if successful, otherwise \b false + */ + bool Write(FileFd &File, char const * const * const Order = NULL, std::vector<Tag> const &Rewrite = std::vector<Tag>()) const; +}; + + +class APT_DEPRECATED_MSG("Use pkgTagFile with the SUPPORT_COMMENTS flag instead") pkgUserTagSection : public pkgTagSection +{ + virtual void TrimRecord(bool BeforeRecord, const char* &End) APT_OVERRIDE; +}; + +/** \class pkgTagFile reads and prepares a deb822 formatted file for parsing + * via #pkgTagSection. The default mode tries to be as fast as possible and + * assumes perfectly valid (machine generated) files like Packages. Support + * for comments e.g. needs to be enabled explicitly. */ +class pkgTagFile +{ + pkgTagFilePrivate * const d; + + APT_HIDDEN bool Fill(); + APT_HIDDEN bool Resize(); + APT_HIDDEN bool Resize(unsigned long long const newSize); + +public: + + bool Step(pkgTagSection &Section); + unsigned long Offset(); + bool Jump(pkgTagSection &Tag,unsigned long long Offset); + + enum Flags + { + STRICT = 0, + SUPPORT_COMMENTS = 1 << 0, + }; + + void Init(FileFd * const F, pkgTagFile::Flags const Flags, unsigned long long Size = 32*1024); + void Init(FileFd * const F,unsigned long long const Size = 32*1024); + + pkgTagFile(FileFd * const F, pkgTagFile::Flags const Flags, unsigned long long Size = 32*1024); + pkgTagFile(FileFd * const F,unsigned long long Size = 32*1024); + virtual ~pkgTagFile(); +}; + +extern const char **TFRewritePackageOrder; +extern const char **TFRewriteSourceOrder; + +APT_IGNORE_DEPRECATED_PUSH +struct APT_DEPRECATED_MSG("Use pkgTagSection::Tag and pkgTagSection::Write() instead") TFRewriteData +{ + const char *Tag; + const char *Rewrite; + const char *NewTag; +}; +APT_DEPRECATED_MSG("Use pkgTagSection::Tag and pkgTagSection::Write() instead") bool TFRewrite(FILE *Output,pkgTagSection const &Tags,const char *Order[], + TFRewriteData *Rewrite); +APT_IGNORE_DEPRECATED_POP + +#endif diff --git a/apt-pkg/update.cc b/apt-pkg/update.cc new file mode 100644 index 0000000..4c64eeb --- /dev/null +++ b/apt-pkg/update.cc @@ -0,0 +1,133 @@ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/acquire-item.h> +#include <apt-pkg/acquire.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/sourcelist.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/update.h> + +#include <string> + +#include <apti18n.h> + /*}}}*/ + +using namespace std; + +// ListUpdate - construct Fetcher and update the cache files /*{{{*/ +// --------------------------------------------------------------------- +/* This is a simple wrapper to update the cache. it will fetch stuff + * from the network (or any other sources defined in sources.list) + */ +bool ListUpdate(pkgAcquireStatus &Stat, + pkgSourceList &List, + int PulseInterval) +{ + pkgAcquire Fetcher(&Stat); + if (Fetcher.GetLock(_config->FindDir("Dir::State::Lists")) == false) + return false; + + // Populate it with the source selection + if (List.GetIndexes(&Fetcher) == false) + return false; + + return AcquireUpdate(Fetcher, PulseInterval, true); +} + /*}}}*/ +// AcquireUpdate - take Fetcher and update the cache files /*{{{*/ +// --------------------------------------------------------------------- +/* This is a simple wrapper to update the cache with a provided acquire + * If you only need control over Status and the used SourcesList use + * ListUpdate method instead. + */ +bool AcquireUpdate(pkgAcquire &Fetcher, int const PulseInterval, + bool const RunUpdateScripts, bool const ListCleanup) +{ + // Run scripts + if (RunUpdateScripts == true) + RunScripts("APT::Update::Pre-Invoke"); + + pkgAcquire::RunResult res; + if(PulseInterval > 0) + res = Fetcher.Run(PulseInterval); + else + res = Fetcher.Run(); + + bool const errorsWereReported = (res == pkgAcquire::Failed); + bool Failed = errorsWereReported; + bool TransientNetworkFailure = false; + bool AllFailed = true; + for (pkgAcquire::ItemIterator I = Fetcher.ItemsBegin(); + I != Fetcher.ItemsEnd(); ++I) + { + switch ((*I)->Status) + { + case pkgAcquire::Item::StatDone: + AllFailed = false; + continue; + case pkgAcquire::Item::StatTransientNetworkError: + TransientNetworkFailure = true; + break; + case pkgAcquire::Item::StatIdle: + case pkgAcquire::Item::StatFetching: + case pkgAcquire::Item::StatError: + case pkgAcquire::Item::StatAuthError: + Failed = true; + break; + } + + (*I)->Finished(); + + if (errorsWereReported) + continue; + + ::URI uri((*I)->DescURI()); + uri.User.clear(); + uri.Password.clear(); + std::string const descUri = std::string(uri); + // Show an error for non-transient failures, otherwise only warn + if ((*I)->Status == pkgAcquire::Item::StatTransientNetworkError) + _error->Warning(_("Failed to fetch %s %s"), descUri.c_str(), + (*I)->ErrorText.c_str()); + else + _error->Error(_("Failed to fetch %s %s"), descUri.c_str(), + (*I)->ErrorText.c_str()); + } + + // Clean out any old list files + // Keep "APT::Get::List-Cleanup" name for compatibility, but + // this is really a global option for the APT library now + if (!TransientNetworkFailure && !Failed && ListCleanup == true && + (_config->FindB("APT::Get::List-Cleanup",true) == true && + _config->FindB("APT::List-Cleanup",true) == true)) + { + if (Fetcher.Clean(_config->FindDir("Dir::State::lists")) == false || + Fetcher.Clean(_config->FindDir("Dir::State::lists") + "partial/") == false) + // something went wrong with the clean + return false; + } + + bool Res = true; + + if (errorsWereReported == true) + Res = false; + else if (TransientNetworkFailure == true) + Res = _error->Warning(_("Some index files failed to download. They have been ignored, or old ones used instead.")); + else if (Failed == true) + Res = _error->Error(_("Some index files failed to download. They have been ignored, or old ones used instead.")); + + // Run the success scripts if all was fine + if (RunUpdateScripts == true) + { + if(AllFailed == false) + RunScripts("APT::Update::Post-Invoke-Success"); + + // Run the other scripts + RunScripts("APT::Update::Post-Invoke"); + } + return Res; +} + /*}}}*/ diff --git a/apt-pkg/update.h b/apt-pkg/update.h new file mode 100644 index 0000000..559516f --- /dev/null +++ b/apt-pkg/update.h @@ -0,0 +1,22 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Update - ListUpdate related code + + ##################################################################### */ + /*}}}*/ + +#ifndef PKGLIB_UPDATE_H +#define PKGLIB_UPDATE_H + +class pkgAcquireStatus; +class pkgSourceList; +class pkgAcquire; + +bool ListUpdate(pkgAcquireStatus &progress, pkgSourceList &List, int PulseInterval=0); +bool AcquireUpdate(pkgAcquire &Fetcher, int const PulseInterval = 0, + bool const RunUpdateScripts = true, bool const ListCleanup = true); + + +#endif diff --git a/apt-pkg/upgrade.cc b/apt-pkg/upgrade.cc new file mode 100644 index 0000000..c9432aa --- /dev/null +++ b/apt-pkg/upgrade.cc @@ -0,0 +1,301 @@ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/algorithms.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/depcache.h> +#include <apt-pkg/edsp.h> +#include <apt-pkg/error.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/progress.h> +#include <apt-pkg/upgrade.h> + +#include <string> + +#include <apti18n.h> + /*}}}*/ + +// DistUpgrade - Distribution upgrade /*{{{*/ +// --------------------------------------------------------------------- +/* This autoinstalls every package and then force installs every + pre-existing package. This creates the initial set of conditions which + most likely contain problems because too many things were installed. + + The problem resolver is used to resolve the problems. + */ +static bool pkgDistUpgrade(pkgDepCache &Cache, OpProgress * const Progress) +{ + std::string const solver = _config->Find("APT::Solver", "internal"); + auto const ret = EDSP::ResolveExternal(solver.c_str(), Cache, EDSP::Request::UPGRADE_ALL, Progress); + if (solver != "internal") + return ret; + + if (Progress != NULL) + Progress->OverallProgress(0, 100, 1, _("Calculating upgrade")); + + pkgDepCache::ActionGroup group(Cache); + + /* Upgrade all installed packages first without autoinst to help the resolver + in versioned or-groups to upgrade the old solver instead of installing + a new one (if the old solver is not the first one [anymore]) */ + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + if (I->CurrentVer != 0) + Cache.MarkInstall(I, false, 0, false); + + if (Progress != NULL) + Progress->Progress(10); + + /* Auto upgrade all installed packages, this provides the basis + for the installation */ + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + if (I->CurrentVer != 0) + Cache.MarkInstall(I, true, 0, false); + + if (Progress != NULL) + Progress->Progress(50); + + /* Now, install each essential package which is not installed + (and not provided by another package in the same name group) */ + std::string essential = _config->Find("pkgCacheGen::Essential", "all"); + if (essential == "all") + { + for (pkgCache::GrpIterator G = Cache.GrpBegin(); G.end() == false; ++G) + { + bool isEssential = false; + bool instEssential = false; + for (pkgCache::PkgIterator P = G.PackageList(); P.end() == false; P = G.NextPkg(P)) + { + if ((P->Flags & pkgCache::Flag::Essential) != pkgCache::Flag::Essential) + continue; + isEssential = true; + if (Cache[P].Install() == true) + { + instEssential = true; + break; + } + } + if (isEssential == false || instEssential == true) + continue; + pkgCache::PkgIterator P = G.FindPreferredPkg(); + Cache.MarkInstall(P, true, 0, false); + } + } + else if (essential != "none") + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + if ((I->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential) + Cache.MarkInstall(I, true, 0, false); + + if (Progress != NULL) + Progress->Progress(55); + + /* We do it again over all previously installed packages to force + conflict resolution on them all. */ + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + if (I->CurrentVer != 0) + Cache.MarkInstall(I, false, 0, false); + + if (Progress != NULL) + Progress->Progress(65); + + pkgProblemResolver Fix(&Cache); + + if (Progress != NULL) + Progress->Progress(95); + + // Hold back held packages. + if (_config->FindB("APT::Ignore-Hold",false) == false) + { + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + if (I->SelectedState == pkgCache::State::Hold) + { + Fix.Protect(I); + Cache.MarkKeep(I, false, false); + } + } + } + + bool const success = Fix.ResolveInternal(false); + if (Progress != NULL) + Progress->Done(); + return success; +} +bool pkgDistUpgrade(pkgDepCache &Cache) +{ + return pkgDistUpgrade(Cache, NULL); +} + /*}}}*/ +// AllUpgradeNoNewPackages - Upgrade but no removals or new pkgs /*{{{*/ +static bool pkgAllUpgradeNoNewPackages(pkgDepCache &Cache, OpProgress * const Progress) +{ + std::string const solver = _config->Find("APT::Solver", "internal"); + constexpr auto flags = EDSP::Request::UPGRADE_ALL | EDSP::Request::FORBID_NEW_INSTALL | EDSP::Request::FORBID_REMOVE; + auto const ret = EDSP::ResolveExternal(solver.c_str(), Cache, flags, Progress); + if (solver != "internal") + return ret; + + if (Progress != NULL) + Progress->OverallProgress(0, 100, 1, _("Calculating upgrade")); + + pkgDepCache::ActionGroup group(Cache); + pkgProblemResolver Fix(&Cache); + + // Upgrade all installed packages + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + if (Cache[I].Install() == true) + Fix.Protect(I); + + if (_config->FindB("APT::Ignore-Hold",false) == false) + if (I->SelectedState == pkgCache::State::Hold) + continue; + + if (I->CurrentVer != 0 && Cache[I].InstallVer != 0) + Cache.MarkInstall(I, false, 0, false); + } + + if (Progress != NULL) + Progress->Progress(50); + + // resolve remaining issues via keep + bool const success = Fix.ResolveByKeepInternal(); + if (Progress != NULL) + Progress->Done(); + return success; +} + /*}}}*/ +// AllUpgradeWithNewInstalls - Upgrade + install new packages as needed /*{{{*/ +// --------------------------------------------------------------------- +/* Right now the system must be consistent before this can be called. + * Upgrade as much as possible without deleting anything (useful for + * stable systems) + */ +static bool pkgAllUpgradeWithNewPackages(pkgDepCache &Cache, OpProgress * const Progress) +{ + std::string const solver = _config->Find("APT::Solver", "internal"); + constexpr auto flags = EDSP::Request::UPGRADE_ALL | EDSP::Request::FORBID_REMOVE; + auto const ret = EDSP::ResolveExternal(solver.c_str(), Cache, flags, Progress); + if (solver != "internal") + return ret; + + if (Progress != NULL) + Progress->OverallProgress(0, 100, 1, _("Calculating upgrade")); + + pkgDepCache::ActionGroup group(Cache); + pkgProblemResolver Fix(&Cache); + + // provide the initial set of stuff we want to upgrade by marking + // all upgradable packages for upgrade + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + if (I->CurrentVer != 0 && Cache[I].InstallVer != 0) + { + if (_config->FindB("APT::Ignore-Hold",false) == false) + if (I->SelectedState == pkgCache::State::Hold) + continue; + + Cache.MarkInstall(I, false, 0, false); + } + } + + if (Progress != NULL) + Progress->Progress(10); + + // then let auto-install loose + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + if (Cache[I].Install()) + Cache.MarkInstall(I, true, 0, false); + + if (Progress != NULL) + Progress->Progress(50); + + // ... but it may remove stuff, we need to clean up afterwards again + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + if (Cache[I].Delete() == true) + Cache.MarkKeep(I, false, false); + + if (Progress != NULL) + Progress->Progress(60); + + // resolve remaining issues via keep + bool const success = Fix.ResolveByKeepInternal(); + if (Progress != NULL) + Progress->Done(); + return success; +} + /*}}}*/ +// AllUpgrade - Upgrade as many packages as possible /*{{{*/ +// --------------------------------------------------------------------- +/* Right now the system must be consistent before this can be called. + It also will not change packages marked for install, it only tries + to install packages not marked for install */ +static bool pkgAllUpgrade(pkgDepCache &Cache, OpProgress * const Progress) +{ + return pkgAllUpgradeNoNewPackages(Cache, Progress); +} +bool pkgAllUpgrade(pkgDepCache &Cache) +{ + return pkgAllUpgrade(Cache, NULL); +} + /*}}}*/ +// MinimizeUpgrade - Minimizes the set of packages to be upgraded /*{{{*/ +// --------------------------------------------------------------------- +/* This simply goes over the entire set of packages and tries to keep + each package marked for upgrade. If a conflict is generated then + the package is restored. */ +bool pkgMinimizeUpgrade(pkgDepCache &Cache) +{ + pkgDepCache::ActionGroup group(Cache); + + if (Cache.BrokenCount() != 0) + return false; + + // We loop for 10 tries to get the minimal set size. + bool Change = false; + unsigned int Count = 0; + do + { + Change = false; + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) + { + // Not interesting + if (Cache[I].Upgrade() == false || Cache[I].NewInstall() == true) + continue; + + // Keep it and see if that is OK + Cache.MarkKeep(I, false, false); + if (Cache.BrokenCount() != 0) + Cache.MarkInstall(I, false, 0, false); + else + { + // If keep didn't actually do anything then there was no change.. + if (Cache[I].Upgrade() == false) + Change = true; + } + } + ++Count; + } + while (Change == true && Count < 10); + + if (Cache.BrokenCount() != 0) + return _error->Error("Internal Error in pkgMinimizeUpgrade"); + + return true; +} + /*}}}*/ +// APT::Upgrade::Upgrade - Upgrade using a specific strategy /*{{{*/ +bool APT::Upgrade::Upgrade(pkgDepCache &Cache, int mode, OpProgress * const Progress) +{ +APT_IGNORE_DEPRECATED_PUSH + if (mode == ALLOW_EVERYTHING) + return pkgDistUpgrade(Cache, Progress); + else if ((mode & ~FORBID_REMOVE_PACKAGES) == 0) + return pkgAllUpgradeWithNewPackages(Cache, Progress); + else if ((mode & ~(FORBID_REMOVE_PACKAGES|FORBID_INSTALL_NEW_PACKAGES)) == 0) + return pkgAllUpgradeNoNewPackages(Cache, Progress); + else + _error->Error("pkgAllUpgrade called with unsupported mode %i", mode); +APT_IGNORE_DEPRECATED_POP + return false; +} + /*}}}*/ diff --git a/apt-pkg/upgrade.h b/apt-pkg/upgrade.h new file mode 100644 index 0000000..5612acf --- /dev/null +++ b/apt-pkg/upgrade.h @@ -0,0 +1,35 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Upgrade - Upgrade/DistUpgrade related code + + ##################################################################### */ + /*}}}*/ + +#ifndef PKGLIB_UPGRADE_H +#define PKGLIB_UPGRADE_H + +#include <apt-pkg/macros.h> +#include <stddef.h> + +class pkgDepCache; +class OpProgress; + +namespace APT { + namespace Upgrade { + // FIXME: make this "enum class UpgradeMode {" once we enable c++11 + enum UpgradeMode { + FORBID_REMOVE_PACKAGES = 1, + FORBID_INSTALL_NEW_PACKAGES = 2, + ALLOW_EVERYTHING = 0 + }; + bool Upgrade(pkgDepCache &Cache, int UpgradeMode, OpProgress * const Progress = NULL); + } +} + +APT_DEPRECATED_MSG("Use APT::Upgrade::Upgrade() instead") bool pkgDistUpgrade(pkgDepCache &Cache); +APT_DEPRECATED_MSG("Use APT::Upgrade::Upgrade() instead") bool pkgAllUpgrade(pkgDepCache &Cache); + +bool pkgMinimizeUpgrade(pkgDepCache &Cache); +#endif diff --git a/apt-pkg/version.cc b/apt-pkg/version.cc new file mode 100644 index 0000000..e0b83e0 --- /dev/null +++ b/apt-pkg/version.cc @@ -0,0 +1,44 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Version - Versioning system.. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/version.h> + +#include <stdlib.h> +#include <string.h> + /*}}}*/ + +static pkgVersioningSystem *VSList[10]; +pkgVersioningSystem **pkgVersioningSystem::GlobalList = VSList; +unsigned long pkgVersioningSystem::GlobalListLen = 0; + +// pkgVS::pkgVersioningSystem - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* Link to the global list of versioning systems supported */ +pkgVersioningSystem::pkgVersioningSystem() : Label(NULL) +{ + VSList[GlobalListLen] = this; + ++GlobalListLen; +} + /*}}}*/ +// pkgVS::GetVS - Find a VS by name /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgVersioningSystem *pkgVersioningSystem::GetVS(const char *Label) +{ + for (unsigned I = 0; I != GlobalListLen; I++) + if (strcmp(VSList[I]->Label,Label) == 0) + return VSList[I]; + return 0; +} + /*}}}*/ + + +pkgVersioningSystem::~pkgVersioningSystem() {} diff --git a/apt-pkg/version.h b/apt-pkg/version.h new file mode 100644 index 0000000..6cb2932 --- /dev/null +++ b/apt-pkg/version.h @@ -0,0 +1,60 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Version - Versioning system.. + + The versioning system represents how versions are compared, represented + and how dependencies are evaluated. As a general rule versioning + systems are not compatible unless specifically allowed by the + TestCompatibility query. + + The versions are stored in a global list of versions, but that is just + so that they can be queried when someone does 'apt-get -v'. + pkgSystem provides the proper means to access the VS for the active + system. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_VERSION_H +#define PKGLIB_VERSION_H + +#include <apt-pkg/strutl.h> +#include <string> + +#ifndef APT_8_CLEANER_HEADERS +using std::string; +#endif + +class pkgVersioningSystem +{ + public: + // Global list of VS's + static pkgVersioningSystem **GlobalList; + static unsigned long GlobalListLen; + static pkgVersioningSystem *GetVS(const char *Label) APT_PURE; + + const char *Label; + + // Compare versions.. + virtual int DoCmpVersion(const char *A,const char *Aend, + const char *B,const char *Bend) = 0; + + virtual bool CheckDep(const char *PkgVer,int Op,const char *DepVer) = 0; + virtual int DoCmpReleaseVer(const char *A,const char *Aend, + const char *B,const char *Bend) = 0; + virtual std::string UpstreamVersion(const char *A) = 0; + + // See if the given VS is compatible with this one.. + virtual bool TestCompatibility(pkgVersioningSystem const &Against) + {return this == &Against;}; + + // Shortcuts + APT_MKSTRCMP(CmpVersion,DoCmpVersion); + APT_MKSTRCMP(CmpReleaseVer,DoCmpReleaseVer); + + pkgVersioningSystem(); + virtual ~pkgVersioningSystem(); +}; + +#endif diff --git a/apt-pkg/versionmatch.cc b/apt-pkg/versionmatch.cc new file mode 100644 index 0000000..340f5a6 --- /dev/null +++ b/apt-pkg/versionmatch.cc @@ -0,0 +1,290 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Version Matching + + This module takes a matching string and a type and locates the version + record that satisfies the constraint described by the matching string. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/error.h> +#include <apt-pkg/pkgcache.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/versionmatch.h> + +#include <string> +#include <ctype.h> +#include <fnmatch.h> +#include <regex.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + /*}}}*/ + +using std::string; + +// VersionMatch::pkgVersionMatch - Constructor /*{{{*/ +// --------------------------------------------------------------------- +/* Break up the data string according to the selected type */ +pkgVersionMatch::pkgVersionMatch(string Data,MatchType Type) : Type(Type) +{ + MatchAll = false; + VerPrefixMatch = false; + RelVerPrefixMatch = false; + + if (Type == None || Data.length() < 1) + return; + + // Cut up the version representation + if (Type == Version) + { + if (Data.end()[-1] == '*') + { + VerPrefixMatch = true; + VerStr = string(Data,0,Data.length()-1); + } + else + VerStr = Data; + return; + } + + if (Type == Release) + { + // All empty = match all + if (Data == "*") + { + MatchAll = true; + return; + } + + // Are we a simple specification? + string::const_iterator I = Data.begin(); + for (; I != Data.end() && *I != '='; ++I); + if (I == Data.end()) + { + // Temporary + if (isdigit(Data[0])) + RelVerStr = Data; + else + RelRelease = Data; + + if (RelVerStr.length() > 0 && RelVerStr.end()[-1] == '*') + { + RelVerPrefixMatch = true; + RelVerStr = string(RelVerStr.begin(),RelVerStr.end()-1); + } + return; + } + + char Spec[300]; + char *Fragments[20]; + snprintf(Spec,sizeof(Spec),"%s",Data.c_str()); + if (TokSplitString(',',Spec,Fragments, + sizeof(Fragments)/sizeof(Fragments[0])) == false) + { + Type = None; + return; + } + + for (unsigned J = 0; Fragments[J] != 0; J++) + { + if (strlen(Fragments[J]) < 3) + continue; + + if (stringcasecmp(Fragments[J],Fragments[J]+2,"v=") == 0) + RelVerStr = Fragments[J]+2; + else if (stringcasecmp(Fragments[J],Fragments[J]+2,"o=") == 0) + RelOrigin = Fragments[J]+2; + else if (stringcasecmp(Fragments[J],Fragments[J]+2,"a=") == 0) + RelArchive = Fragments[J]+2; + else if (stringcasecmp(Fragments[J],Fragments[J]+2,"n=") == 0) + RelCodename = Fragments[J]+2; + else if (stringcasecmp(Fragments[J],Fragments[J]+2,"l=") == 0) + RelLabel = Fragments[J]+2; + else if (stringcasecmp(Fragments[J],Fragments[J]+2,"c=") == 0) + RelComponent = Fragments[J]+2; + else if (stringcasecmp(Fragments[J],Fragments[J]+2,"b=") == 0) + RelArchitecture = Fragments[J]+2; + } + + if (RelVerStr.end()[-1] == '*') + { + RelVerPrefixMatch = true; + RelVerStr = string(RelVerStr.begin(),RelVerStr.end()-1); + } + return; + } + + if (Type == Origin) + { + if (Data[0] == '"' && Data.length() >= 2 && Data.end()[-1] == '"') + OrSite = Data.substr(1, Data.length() - 2); + else + OrSite = Data; + return; + } +} + /*}}}*/ +// VersionMatch::MatchVer - Match a version string with prefixing /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgVersionMatch::MatchVer(const char *A,string B,bool Prefix) +{ + if (A == NULL) + return false; + + const char *Ab = A; + const char *Ae = Ab + strlen(A); + + // Strings are not a compatible size. + if (((unsigned)(Ae - Ab) != B.length() && Prefix == false) || + (unsigned)(Ae - Ab) < B.length()) + return false; + + // Match (leading?) + if (stringcasecmp(B,Ab,Ab + B.length()) == 0) + return true; + + return false; +} + /*}}}*/ +// VersionMatch::Find - Locate the best match for the select type /*{{{*/ +// --------------------------------------------------------------------- +/* */ +pkgCache::VerIterator pkgVersionMatch::Find(pkgCache::PkgIterator Pkg) +{ + pkgCache::VerIterator Ver = Pkg.VersionList(); + for (; Ver.end() == false; ++Ver) + { + if (VersionMatches(Ver)) + return Ver; + } + + // This will be Ended by now. + return Ver; +} + /*}}}*/ + +// VersionMatch::Find - Locate the best match for the select type /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool pkgVersionMatch::VersionMatches(pkgCache::VerIterator Ver) +{ + if (Type == Version) + { + if (MatchVer(Ver.VerStr(),VerStr,VerPrefixMatch) == true) + return true; + if (ExpressionMatches(VerStr, Ver.VerStr()) == true) + return true; + return false; + } + + for (pkgCache::VerFileIterator VF = Ver.FileList(); VF.end() == false; ++VF) + if (FileMatch(VF.File()) == true) + return true; + + return false; +} + /*}}}*/ + +#ifndef FNM_CASEFOLD +#define FNM_CASEFOLD 0 +#endif + +bool pkgVersionMatch::ExpressionMatches(const char *pattern, const char *string)/*{{{*/ +{ + if (pattern == NULL || string == NULL) + return false; + if (pattern[0] == '/') { + size_t length = strlen(pattern); + if (pattern[length - 1] == '/') { + bool res = false; + regex_t preg; + char *regex = strdup(pattern + 1); + regex[length - 2] = '\0'; + if (regcomp(&preg, regex, REG_EXTENDED | REG_ICASE) != 0) { + _error->Warning("Invalid regular expression: %s", regex); + } else if (regexec(&preg, string, 0, NULL, 0) == 0) { + res = true; + } + free(regex); + regfree(&preg); + return res; + } + } + return fnmatch(pattern, string, FNM_CASEFOLD) == 0; +} +bool pkgVersionMatch::ExpressionMatches(const std::string& pattern, const char *string) +{ + return ExpressionMatches(pattern.c_str(), string); +} + /*}}}*/ +// VersionMatch::FileMatch - Match against an index file /*{{{*/ +// --------------------------------------------------------------------- +/* This matcher checks against the release file and the origin location + to see if the constraints are met. */ +bool pkgVersionMatch::FileMatch(pkgCache::PkgFileIterator File) +{ + if (Type == Release) + { + if (MatchAll == true) + return true; + +/* cout << RelVerStr << ',' << RelOrigin << ',' << RelArchive << ',' << RelLabel << endl; + cout << File.Version() << ',' << File.Origin() << ',' << File.Archive() << ',' << File.Label() << endl;*/ + + if (RelVerStr.empty() == true && RelOrigin.empty() == true && + RelArchive.empty() == true && RelLabel.empty() == true && + RelRelease.empty() == true && RelCodename.empty() == true && + RelComponent.empty() == true && RelArchitecture.empty() == true) + return false; + + if (RelVerStr.empty() == false) + if (MatchVer(File.Version(),RelVerStr,RelVerPrefixMatch) == false && + ExpressionMatches(RelVerStr, File.Version()) == false) + return false; + if (RelOrigin.empty() == false) + if (!ExpressionMatches(RelOrigin,File.Origin())) + return false; + if (RelArchive.empty() == false) + if (!ExpressionMatches(RelArchive,File.Archive())) + return false; + if (RelCodename.empty() == false) + if (!ExpressionMatches(RelCodename,File.Codename())) + return false; + if (RelRelease.empty() == false) + if (!ExpressionMatches(RelRelease,File.Archive()) && + !ExpressionMatches(RelRelease,File.Codename())) + return false; + if (RelLabel.empty() == false) + if (!ExpressionMatches(RelLabel,File.Label())) + return false; + if (RelComponent.empty() == false) + if (!ExpressionMatches(RelComponent,File.Component())) + return false; + if (RelArchitecture.empty() == false) + if (!ExpressionMatches(RelArchitecture,File.Architecture())) + return false; + return true; + } + + if (Type == Origin) + { + if (OrSite.empty() == false) { + if (File.Site() == NULL) + return false; + } + else if (File->Release == 0)// only 'bad' files like dpkg.status file has no release file + return false; + return (ExpressionMatches(OrSite, File.Site())); /* both strings match */ + } + + return false; +} + /*}}}*/ diff --git a/apt-pkg/versionmatch.h b/apt-pkg/versionmatch.h new file mode 100644 index 0000000..c9e4367 --- /dev/null +++ b/apt-pkg/versionmatch.h @@ -0,0 +1,80 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Version Matching + + This module takes a matching string and a type and locates the version + record that satisfies the constraint described by the matching string. + + Version: 1.2* + Release: o=Debian,v=2.1*,c=main + Release: v=2.1* + Release: a=testing + Release: n=squeeze + Release: * + Origin: ftp.debian.org + + Release may be a complex type that can specify matches for any of: + Version (v= with prefix) + Origin (o=) + Archive (a=) eg, unstable, testing, stable + Codename (n=) e.g. etch, lenny, squeeze, sid + Label (l=) + Component (c=) + Binary Architecture (b=) + If there are no equals signs in the string then it is scanned in short + form - if it starts with a number it is Version otherwise it is an + Archive or a Codename. + + Release may be a '*' to match all releases. + + ##################################################################### */ + /*}}}*/ +#ifndef PKGLIB_VERSIONMATCH_H +#define PKGLIB_VERSIONMATCH_H + +#include <apt-pkg/pkgcache.h> + +#include <string> + +#ifndef APT_8_CLEANER_HEADERS +using std::string; +#endif + +class pkgVersionMatch +{ + // Version Matching + std::string VerStr; + bool VerPrefixMatch; + + // Release Matching + std::string RelVerStr; + bool RelVerPrefixMatch; + std::string RelOrigin; + std::string RelRelease; + std::string RelCodename; + std::string RelArchive; + std::string RelLabel; + std::string RelComponent; + std::string RelArchitecture; + bool MatchAll; + + // Origin Matching + std::string OrSite; + + public: + + enum MatchType {None = 0,Version,Release,Origin} Type; + + bool MatchVer(const char *A,std::string B,bool Prefix) APT_PURE; + static bool ExpressionMatches(const char *pattern, const char *string); + static bool ExpressionMatches(const std::string& pattern, const char *string); + bool FileMatch(pkgCache::PkgFileIterator File); + pkgCache::VerIterator Find(pkgCache::PkgIterator Pkg); + bool VersionMatches(pkgCache::VerIterator Ver); + + pkgVersionMatch(std::string Data,MatchType Type); +}; + +#endif |