diff --git a/CMake/FindZstd.cmake b/CMake/FindZstd.cmake new file mode 100644 index 000000000..68118049b --- /dev/null +++ b/CMake/FindZstd.cmake @@ -0,0 +1,25 @@ +# - Try to find ZSTD +# Once done, this will define +# +# ZSTD_FOUND - system has ZSTD +# ZSTD_INCLUDE_DIRS - the ZSTD include directories +# ZSTD_LIBRARIES - the ZSTD library +find_package(PkgConfig) + +pkg_check_modules(ZSTD_PKGCONF libzstd) + +find_path(ZSTD_INCLUDE_DIRS + NAMES zstd.h + PATHS ${ZSTD_PKGCONF_INCLUDE_DIRS} +) + + +find_library(ZSTD_LIBRARIES + NAMES zstd + PATHS ${ZSTD_PKGCONF_LIBRARY_DIRS} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(ZSTD DEFAULT_MSG ZSTD_INCLUDE_DIRS ZSTD_LIBRARIES) + +mark_as_advanced(ZSTD_INCLUDE_DIRS ZSTD_LIBRARIES) diff --git a/CMake/config.h.in b/CMake/config.h.in index cfaa14ed1..bd0da8649 100644 --- a/CMake/config.h.in +++ b/CMake/config.h.in @@ -17,6 +17,9 @@ /* Define if we have the lz4 library for lz4 */ #cmakedefine HAVE_LZ4 +/* Define if we have the zstd library for zst */ +#cmakedefine HAVE_ZSTD + /* Define if we have the udev library */ #cmakedefine HAVE_UDEV diff --git a/CMakeLists.txt b/CMakeLists.txt index b723b0ead..a79ce99d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,12 @@ if (LZ4_FOUND) set(HAVE_LZ4 1) endif() +find_package(Zstd REQUIRED) +if (ZSTD_FOUND) + set(HAVE_ZSTD 1) +endif() + + find_package(Udev) if (UDEV_FOUND) set(HAVE_UDEV 1) diff --git a/Dockerfile b/Dockerfile index b7af5f629..35db15863 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:testing +FROM ubuntu:bionic COPY . /tmp WORKDIR /tmp RUN sed -i s#://deb.debian.org#://cdn-fastly.deb.debian.org# /etc/apt/sources.list \ @@ -7,5 +7,6 @@ RUN sed -i s#://deb.debian.org#://cdn-fastly.deb.debian.org# /etc/apt/sources.li && env DEBIAN_FRONTEND=noninteractive apt-get install build-essential ccache ninja-build expect curl git -q -y \ && env DEBIAN_FRONTEND=noninteractive ./prepare-release travis-ci \ && dpkg-reconfigure ccache \ + && rm -f /etc/dpkg/dpkg.cfg.d/excludes \ && rm -r /tmp/* \ && apt-get clean diff --git a/README.json-hooks.md b/README.json-hooks.md new file mode 100644 index 000000000..1e05c1892 --- /dev/null +++ b/README.json-hooks.md @@ -0,0 +1,156 @@ +## JSON Hooks + +APT 1.6 introduces support for hooks that talk JSON-RPC 2.0. Hooks act +as a server, and APT as a client. + +## Wire protocol + +APT communicates with hooks via a UNIX domain socket in file descriptor +`$APT_HOOK_SOCKET`. The transport is a byte stream (SOCK_STREAM). + +The byte stream contains multiple JSON objects, each representing a +JSON-RPC request or response, and each terminated by an empty line +(`\n\n`). Therefore, JSON objects containing empty lines may not be +used. + +For protocol version `0.1`, each JSON object must be encoded on a single +line. + +## Lifecycle + +The general life of a hook is as following. + +1. Hook is started +2. Hello handshake is exchanged +3. One or more calls or notifications are sent from apt to the hook +4. Bye notification is send + +It is unspecified whether a hook is sent one or more messages. For +example, a hook may be started only once for the lifetime of the apt +process and receive multiple notificatgions, but a hook may also be +started multiple times. Hooks should thus be stateless. + +## JSON messages + +### Hello handshake + +APT performs a call to the method `org.debian.apt.hooks.hello` with +the named parameter `versions` containing a list of supported protocol +versions. The hook picks the version it supports. The current version +is `"0.1"`, and support for that version is mandatory. + +*Example*: + +1. APT: + ```{"jsonrpc":"2.0","method":"org.debian.apt.hooks.hello","id":0,"params":{"versions":["0.1"]}}``` + + +2. Hook: + ```{"jsonrpc":"2.0","id":0,"result":{"version":"0.1"}}``` + +### Bye notification + +Before closing the connection, APT sends a notification for the +method `org.debian.apt.hooks.bye`. + +### Hook notification + +The following methods are supported: + +1. `org.debian.apt.hooks.pre-download` +1. `org.debian.apt.hooks.post-install` +1. `org.debian.apt.hooks.fail-install` +1. `org.debian.apt.hooks.pre-search` +1. `org.debian.apt.hooks.post-search` +1. `org.debian.apt.hooks.fail-search` + +They can be registered by adding them to the list: + +```AptCli::Hooks::``` + +where `` is the name of the hook. + +#### Parameters + +*command*: The command used on the command-line. For example, `"purge"`. + +*search-terms*: Any non-option arguments given to the command. + +*unknown-packages*: For non-search hooks, a subset of *search-terms* +that APT could not find packages in the cache for. + +*packages*: An array of modified packages. This is mostly useful for +install. Each package has the following attributes: + +- *id*: An unsigned integer describing the package +- *name*: The name of the package +- *architecture*: The architecture of the package. For `"all"` packages, this will be the native architecture; + use per-version architecture fields to see `"all"`. + +- *mode*: One of `install`, `deinstall`, `purge`, or `keep`. `keep` + is not exposed in 0.1. To determine an upgrade, check + that a current version is installed. +- *automatic*: Whether the package is/will be automatically installed +- *versions*: An array with up to 3 fields: + + - *candidate*: The candidate version + - *install*: The version to be installed + - *current*: The version currently installed + + Each version is represented as an object with the following fields: + + - *id*: An unsigned integer + - *version*: The version as a string + - *architecture*: Architecture of the version + - *pin*: The pin priority + +#### Example + +```json +{ + "jsonrpc": "2.0", + "method": "org.debian.apt.hooks.pre-install", + "params": { + "command": "purge", + "search-terms": [ + "petname-", + "lxd+" + ], + "packages": [ + { + "id": 1500, + "name": "ebtables", + "architecture": "amd64", + "mode": "install", + "automatic": 1, + "versions": { + "candidate": { + "id": 376, + "version": "2.0.10.4-3.5ubuntu2", + "architecture": "amd64", + "pin": 990 + }, + "install": { + "id": 376, + "version": "2.0.10.4-3.5ubuntu2", + "architecture": "amd64", + "pin": 990 + } + } + } + ] + } +} +``` + +#### Compatibility note +Future versions of APT might make these calls instead of notifications. + +## Evolution of this protocol +New incompatible versions may be introduced with each new feature +release of apt (1.7, 1.8, etc). No backward compatibility is promised +until protocol 1.0: New stable feature releases may support a newer +protocol version only (for example, 1.7 may only support 0.2). + +Additional fields may be added to objects without bumping the protocol +version. diff --git a/apt-inst/deb/debfile.cc b/apt-inst/deb/debfile.cc index 8eef446bb..6f7cf5691 100644 --- a/apt-inst/deb/debfile.cc +++ b/apt-inst/deb/debfile.cc @@ -50,7 +50,9 @@ debDebFile::debDebFile(FileFd &File) : File(File), AR(File) if (!CheckMember("control.tar") && !CheckMember("control.tar.gz") && - !CheckMember("control.tar.xz")) { + !CheckMember("control.tar.xz") && + !CheckMember("control.tar.zst")) + { _error->Error(_("This is not a valid DEB archive, missing '%s' member"), "control.tar"); return; } @@ -59,7 +61,9 @@ debDebFile::debDebFile(FileFd &File) : File(File), AR(File) !CheckMember("data.tar.gz") && !CheckMember("data.tar.bz2") && !CheckMember("data.tar.lzma") && - !CheckMember("data.tar.xz")) { + !CheckMember("data.tar.xz") && + !CheckMember("data.tar.zst")) + { _error->Error(_("This is not a valid DEB archive, missing '%s' member"), "data.tar"); return; } diff --git a/apt-pkg/CMakeLists.txt b/apt-pkg/CMakeLists.txt index 2f5ad3200..fdf27f92d 100644 --- a/apt-pkg/CMakeLists.txt +++ b/apt-pkg/CMakeLists.txt @@ -44,6 +44,7 @@ target_include_directories(apt-pkg ${BZIP2_INCLUDE_DIR} ${LZMA_INCLUDE_DIRS} ${LZ4_INCLUDE_DIRS} + ${ZSTD_INCLUDE_DIRS} $<$:${UDEV_INCLUDE_DIRS}> ${ICONV_INCLUDE_DIRS} ) @@ -55,6 +56,7 @@ target_link_libraries(apt-pkg ${BZIP2_LIBRARIES} ${LZMA_LIBRARIES} ${LZ4_LIBRARIES} + ${ZSTD_LIBRARIES} $<$:${UDEV_LIBRARIES}> ${ICONV_LIBRARIES} ) diff --git a/apt-pkg/aptconfiguration.cc b/apt-pkg/aptconfiguration.cc index 0421ea949..61e53ec3a 100644 --- a/apt-pkg/aptconfiguration.cc +++ b/apt-pkg/aptconfiguration.cc @@ -39,6 +39,7 @@ static void setDefaultConfigurationForCompressors() { _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"); @@ -67,6 +68,7 @@ static void setDefaultConfigurationForCompressors() { _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 /*{{{*/ @@ -369,6 +371,12 @@ const Configuration::getCompressors(bool const Cached) { # 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 diff --git a/apt-pkg/contrib/fileutl.cc b/apt-pkg/contrib/fileutl.cc index f8f7a478c..d7ab28682 100644 --- a/apt-pkg/contrib/fileutl.cc +++ b/apt-pkg/contrib/fileutl.cc @@ -67,6 +67,9 @@ #ifdef HAVE_LZ4 #include #endif +#ifdef HAVE_ZSTD +#include +#endif #include #include @@ -1714,6 +1717,217 @@ public: 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 const &Args) + { + for (auto a = Args.rbegin(); a != Args.rend(); ++a) + { + if (a->empty() == false && (*a)[0] == '-' && (*a)[1] != '-') + { + auto const notANumber = (a + 1)->find_first_not_of("0123456789"); + if (notANumber != std::string::npos) + continue; + + return (uint32_t)stoi(a->substr(1)); + } + } + return 19; + } + + explicit ZstdFileFdPrivate(FileFd *const filefd) : FileFdPrivate(filefd), dctx(nullptr), cctx(nullptr) {} + virtual ~ZstdFileFdPrivate() + { + InternalClose(""); + } +#endif }; /*}}}*/ class APT_HIDDEN LzmaFileFdPrivate: public FileFdPrivate { /*{{{*/ @@ -2212,6 +2426,7 @@ bool FileFd::Open(string FileName,unsigned int const Mode,CompressMode Compress, 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 @@ -2329,6 +2544,7 @@ bool FileFd::OpenDescriptor(int Fd, unsigned int const Mode, CompressMode Compre 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) @@ -2393,6 +2609,9 @@ bool FileFd::OpenInternDescriptor(unsigned int const Mode, APT::Configuration::C #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); diff --git a/apt-pkg/contrib/fileutl.h b/apt-pkg/contrib/fileutl.h index 699b8b802..6249b7c16 100644 --- a/apt-pkg/contrib/fileutl.h +++ b/apt-pkg/contrib/fileutl.h @@ -46,6 +46,7 @@ class FileFd friend class Bz2FileFdPrivate; friend class LzmaFileFdPrivate; friend class Lz4FileFdPrivate; + friend class ZstdFileFdPrivate; friend class DirectFileFdPrivate; friend class PipedFileFdPrivate; protected: @@ -76,8 +77,19 @@ class FileFd ReadOnlyGzip, WriteAtomic = ReadWrite | Create | Atomic }; - enum CompressMode { Auto = 'A', None = 'N', Extension = 'E', Gzip = 'G', Bzip2 = 'B', Lzma = 'L', Xz = 'X', Lz4='4' }; - + 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; diff --git a/apt-pkg/init.cc b/apt-pkg/init.cc index 5c34113e7..927a5cca0 100644 --- a/apt-pkg/init.cc +++ b/apt-pkg/init.cc @@ -207,7 +207,7 @@ bool pkgInitConfig(Configuration &Cnf) Cnf.CndSet("Acquire::Changelogs::URI::Origin::Debian", "http://metadata.ftp-master.debian.org/changelogs/@CHANGEPATH@_changelog"); Cnf.CndSet("Acquire::Changelogs::URI::Origin::Tanglu", "http://metadata.tanglu.org/changelogs/@CHANGEPATH@_changelog"); - Cnf.CndSet("Acquire::Changelogs::URI::Origin::Ubuntu", "http://changelogs.ubuntu.com/changelogs/pool/@CHANGEPATH@/changelog"); + Cnf.CndSet("Acquire::Changelogs::URI::Origin::Ubuntu", "https://changelogs.ubuntu.com/changelogs/pool/@CHANGEPATH@/changelog"); Cnf.CndSet("Acquire::Changelogs::URI::Origin::Ultimedia", "http://packages.ultimediaos.com/changelogs/pool/@CHANGEPATH@/changelog.txt"); Cnf.CndSet("Acquire::Changelogs::AlwaysOnline::Origin::Ubuntu", true); diff --git a/apt-pkg/pkgcache.cc b/apt-pkg/pkgcache.cc index ea34db469..119240cea 100644 --- a/apt-pkg/pkgcache.cc +++ b/apt-pkg/pkgcache.cc @@ -58,7 +58,7 @@ pkgCache::Header::Header() /* Whenever the structures change the major version should be bumped, whenever the generator changes the minor version should be bumped. */ - APT_HEADER_SET(MajorVersion, 12); + APT_HEADER_SET(MajorVersion, 13); APT_HEADER_SET(MinorVersion, 0); APT_HEADER_SET(Dirty, false); diff --git a/apt-pkg/pkgsystem.cc b/apt-pkg/pkgsystem.cc index aa94418c6..5d74e882b 100644 --- a/apt-pkg/pkgsystem.cc +++ b/apt-pkg/pkgsystem.cc @@ -72,7 +72,7 @@ std::vector pkgSystem::ArchitecturesSupported() const /*{{{*/ return {}; } /*}}}*/ -// pkgSystem::Set/GetVersionMapping - for internal/external communcation/*{{{*/ +// pkgSystem::Set/GetVersionMapping - for internal/external communication/*{{{*/ void pkgSystem::SetVersionMapping(map_id_t const in, map_id_t const out) { if (in == out) diff --git a/apt-private/private-cacheset.cc b/apt-private/private-cacheset.cc index 3d1a2b91c..95a16f8ba 100644 --- a/apt-private/private-cacheset.cc +++ b/apt-private/private-cacheset.cc @@ -358,9 +358,15 @@ APT::VersionSet CacheSetHelperAPTGet::tryVirtualPackage(pkgCacheFile &Cache, pkg } pkgCache::PkgIterator CacheSetHelperAPTGet::canNotFindPkgName(pkgCacheFile &Cache, std::string const &str) { - pkgCache::PkgIterator const Pkg = canNotFindPkgName_impl(Cache, str); + pkgCache::PkgIterator Pkg = canNotFindPkgName_impl(Cache, str); if (Pkg.end()) - return APT::CacheSetHelper::canNotFindPkgName(Cache, str); + { + Pkg = APT::CacheSetHelper::canNotFindPkgName(Cache, str); + if (Pkg.end() && ShowError) + { + notFound.insert(str); + } + } return Pkg; } /*}}}*/ diff --git a/apt-private/private-cacheset.h b/apt-private/private-cacheset.h index 7bf486b9e..d20d00b68 100644 --- a/apt-private/private-cacheset.h +++ b/apt-private/private-cacheset.h @@ -94,9 +94,9 @@ class CacheSetHelperAPTGet : public APT::CacheSetHelper { bool explicitlyNamed; APT::PackageSet virtualPkgs; - public: std::list > selectedByRelease; + std::set notFound; explicit CacheSetHelperAPTGet(std::ostream &out); diff --git a/apt-private/private-install.cc b/apt-private/private-install.cc index b24b96351..362a356f2 100644 --- a/apt-private/private-install.cc +++ b/apt-private/private-install.cc @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -569,10 +570,11 @@ bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, CacheFile &Cache, int bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector &VolatileCmdL, CacheFile &Cache, int UpgradeMode) { std::map verset; - return DoCacheManipulationFromCommandLine(CmdL, VolatileCmdL, Cache, verset, UpgradeMode); + std::set UnknownPackages; + return DoCacheManipulationFromCommandLine(CmdL, VolatileCmdL, Cache, verset, UpgradeMode, UnknownPackages); } bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector &VolatileCmdL, CacheFile &Cache, - std::map &verset, int UpgradeMode) + std::map &verset, int UpgradeMode, std::set &UnknownPackages) { // Enter the special broken fixing mode if the user specified arguments bool BrokenFix = false; @@ -621,6 +623,8 @@ bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vectorPendingError() == true) { helper.showVirtualPackageErrors(Cache); @@ -726,8 +730,13 @@ bool DoInstall(CommandLine &CmdL) return false; std::map verset; - if(!DoCacheManipulationFromCommandLine(CmdL, VolatileCmdL, Cache, verset, 0)) + std::set UnknownPackages; + + if (!DoCacheManipulationFromCommandLine(CmdL, VolatileCmdL, Cache, verset, 0, UnknownPackages)) + { + RunJsonHook("AptCli::Hooks::Fail-Install", "org.debian.apt.hooks.fail-install", CmdL.FileList, Cache, UnknownPackages); return false; + } /* Print out a list of packages that are going to be installed extra to what the user asked */ @@ -828,12 +837,22 @@ bool DoInstall(CommandLine &CmdL) always_true, string_ident, verbose_show_candidate); } + RunJsonHook("AptCli::Hooks::Pre-Download", "org.debian.apt.hooks.pre-download", CmdL.FileList, Cache); + + bool result; // See if we need to prompt // FIXME: check if really the packages in the set are going to be installed if (Cache->InstCount() == verset[MOD_INSTALL].size() && Cache->DelCount() == 0) - return InstallPackages(Cache,false,false); + result = InstallPackages(Cache, false, false); + else + result = InstallPackages(Cache, false); + + if (result) + result = RunJsonHook("AptCli::Hooks::Post-Install", "org.debian.apt.hooks.post-install", CmdL.FileList, Cache); + else + /* not a result */ RunJsonHook("AptCli::Hooks::Fail-Install", "org.debian.apt.hooks.fail-install", CmdL.FileList, Cache); - return InstallPackages(Cache,false); + return result; } /*}}}*/ diff --git a/apt-private/private-install.h b/apt-private/private-install.h index c8b065331..2d27756c9 100644 --- a/apt-private/private-install.h +++ b/apt-private/private-install.h @@ -18,7 +18,7 @@ class pkgProblemResolver; APT_PUBLIC bool DoInstall(CommandLine &Cmd); bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector &VolatileCmdL, CacheFile &Cache, - std::map &verset, int UpgradeMode); + std::map &verset, int UpgradeMode, std::set &UnknownPackages); bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector &VolatileCmdL, CacheFile &Cache, int UpgradeMode); bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, CacheFile &Cache, int UpgradeMode); diff --git a/apt-private/private-json-hooks.cc b/apt-private/private-json-hooks.cc new file mode 100644 index 000000000..07c89ca23 --- /dev/null +++ b/apt-private/private-json-hooks.cc @@ -0,0 +1,425 @@ +/* + * private-json-hooks.cc - 2nd generation, JSON-RPC, hooks for APT + * + * Copyright (c) 2018 Canonical Ltd + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +/** + * @brief Simple JSON writer + * + * This performs no error checking, or string escaping, be careful. + */ +class APT_HIDDEN JsonWriter +{ + std::ostream &os; + std::locale old_locale; + + enum write_state + { + empty, + in_array_first_element, + in_array, + in_object_first_key, + in_object_key, + in_object_val + } state = empty; + + std::stack old_states; + + void maybeComma() + { + switch (state) + { + case empty: + break; + case in_object_val: + state = in_object_key; + break; + case in_object_key: + state = in_object_val; + os << ','; + break; + case in_array: + os << ','; + break; + case in_array_first_element: + state = in_array; + break; + case in_object_first_key: + state = in_object_val; + break; + default: + abort(); + } + } + + void pushState(write_state state) + { + old_states.push(this->state); + this->state = state; + } + + void popState() + { + this->state = old_states.top(); + } + + public: + JsonWriter(std::ostream &os) : os(os) { old_locale = os.imbue(std::locale::classic()); } + ~JsonWriter() { os.imbue(old_locale); } + JsonWriter &beginArray() + { + maybeComma(); + pushState(in_array_first_element); + os << '['; + return *this; + } + JsonWriter &endArray() + { + popState(); + os << ']'; + return *this; + } + JsonWriter &beginObject() + { + maybeComma(); + pushState(in_object_first_key); + os << '{'; + return *this; + } + JsonWriter &endObject() + { + popState(); + os << '}'; + return *this; + } + JsonWriter &name(std::string const &name) + { + maybeComma(); + os << '"' << name << '"' << ':'; + return *this; + } + JsonWriter &value(std::string const &value) + { + maybeComma(); + os << '"' << value << '"'; + return *this; + } + JsonWriter &value(const char *value) + { + maybeComma(); + os << '"' << value << '"'; + return *this; + } + JsonWriter &value(int value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(long value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(long long value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(unsigned long long value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(unsigned long value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(unsigned int value) + { + maybeComma(); + os << value; + return *this; + } + JsonWriter &value(bool value) + { + maybeComma(); + os << (value ? "true" : "false"); + return *this; + } + JsonWriter &value(double value) + { + maybeComma(); + os << value; + return *this; + } +}; + +/** + * @brief Wrtie a VerIterator to a JsonWriter + */ +static void verIterToJson(JsonWriter &writer, CacheFile &Cache, pkgCache::VerIterator const &Ver) +{ + writer.beginObject(); + writer.name("id").value(Ver->ID); + writer.name("version").value(Ver.VerStr()); + writer.name("architecture").value(Ver.Arch()); + writer.name("pin").value(Cache->GetPolicy().GetPriority(Ver)); + writer.endObject(); +} + +/** + * @brief Copy of debSystem::DpkgChrootDirectory() + * @todo Remove + */ +static void DpkgChrootDirectory() +{ + std::string const chrootDir = _config->FindDir("DPkg::Chroot-Directory"); + if (chrootDir == "/") + return; + std::cerr << "Chrooting into " << chrootDir << std::endl; + if (chroot(chrootDir.c_str()) != 0) + _exit(100); + if (chdir("/") != 0) + _exit(100); +} + +/** + * @brief Send a notification to the hook's stream + */ +static void NotifyHook(std::ostream &os, std::string const &method, const char **FileList, CacheFile &Cache, std::set const &UnknownPackages) +{ + SortedPackageUniverse Universe(Cache); + JsonWriter jsonWriter{os}; + + jsonWriter.beginObject(); + + jsonWriter.name("jsonrpc").value("2.0"); + jsonWriter.name("method").value(method); + + /* Build params */ + jsonWriter.name("params").beginObject(); + jsonWriter.name("command").value(FileList[0]); + jsonWriter.name("search-terms").beginArray(); + for (int i = 1; FileList[i] != NULL; i++) + jsonWriter.value(FileList[i]); + jsonWriter.endArray(); + jsonWriter.name("unknown-packages").beginArray(); + for (auto const &PkgName : UnknownPackages) + jsonWriter.value(PkgName); + jsonWriter.endArray(); + + jsonWriter.name("packages").beginArray(); + for (auto const &Pkg : Universe) + { + switch (Cache[Pkg].Mode) + { + case pkgDepCache::ModeInstall: + case pkgDepCache::ModeDelete: + break; + default: + continue; + } + + jsonWriter.beginObject(); + + jsonWriter.name("id").value(Pkg->ID); + jsonWriter.name("name").value(Pkg.Name()); + jsonWriter.name("architecture").value(Pkg.Arch()); + + switch (Cache[Pkg].Mode) + { + case pkgDepCache::ModeInstall: + jsonWriter.name("mode").value("install"); + break; + case pkgDepCache::ModeDelete: + jsonWriter.name("mode").value(Cache[Pkg].Purge() ? "purge" : "deinstall"); + break; + default: + continue; + } + jsonWriter.name("automatic").value(bool(Cache[Pkg].Flags & pkgCache::Flag::Auto)); + + jsonWriter.name("versions").beginObject(); + + if (Cache[Pkg].CandidateVer != nullptr) + verIterToJson(jsonWriter.name("candidate"), Cache, Cache[Pkg].CandidateVerIter(Cache)); + if (Cache[Pkg].InstallVer != nullptr) + verIterToJson(jsonWriter.name("install"), Cache, Cache[Pkg].InstVerIter(Cache)); + if (Pkg->CurrentVer != 0) + verIterToJson(jsonWriter.name("current"), Cache, Pkg.CurrentVer()); + + jsonWriter.endObject(); + + jsonWriter.endObject(); + } + + jsonWriter.endArray(); // packages + jsonWriter.endObject(); // params + jsonWriter.endObject(); // main +} + +/// @brief Build the hello handshake message for 0.1 protocol +static std::string BuildHelloMessage() +{ + std::stringstream Hello; + JsonWriter(Hello).beginObject().name("jsonrpc").value("2.0").name("method").value("org.debian.apt.hooks.hello").name("id").value(0).name("params").beginObject().name("versions").beginArray().value("0.1").endArray().endObject().endObject(); + + return Hello.str(); +} + +/// @brief Build the bye notification for 0.1 protocol +static std::string BuildByeMessage() +{ + std::stringstream Bye; + JsonWriter(Bye).beginObject().name("jsonrpc").value("2.0").name("method").value("org.debian.apt.hooks.bye").name("params").beginObject().endObject().endObject(); + + return Bye.str(); +} + +/// @brief Run the Json hook processes in the given option. +bool RunJsonHook(std::string const &option, std::string const &method, const char **FileList, CacheFile &Cache, std::set const &UnknownPackages) +{ + std::stringstream ss; + NotifyHook(ss, method, FileList, Cache, UnknownPackages); + std::string TheData = ss.str(); + std::string HelloData = BuildHelloMessage(); + std::string ByeData = BuildByeMessage(); + + bool result = true; + + Configuration::Item const *Opts = _config->Tree(option.c_str()); + 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; + + // Create the pipes + std::set KeepFDs; + MergeKeepFdsFromConfiguration(KeepFDs); + int Pipes[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, Pipes) != 0) + { + result = _error->Errno("pipe", "Failed to create IPC pipe to subprocess"); + break; + } + + int InfoFD = 3; + + if (InfoFD != Pipes[0]) + SetCloseExec(Pipes[0], true); + else + KeepFDs.insert(Pipes[0]); + + SetCloseExec(Pipes[1], true); + + // Purified Fork for running the script + pid_t Process = ExecFork(KeepFDs); + if (Process == 0) + { + // Setup the FDs + dup2(Pipes[0], InfoFD); + SetCloseExec(STDOUT_FILENO, false); + SetCloseExec(STDIN_FILENO, false); + SetCloseExec(STDERR_FILENO, false); + + string hookfd; + strprintf(hookfd, "%d", InfoFD); + setenv("APT_HOOK_SOCKET", hookfd.c_str(), 1); + + DpkgChrootDirectory(); + const char *Args[4]; + Args[0] = "/bin/sh"; + Args[1] = "-c"; + Args[2] = Opts->Value.c_str(); + Args[3] = 0; + execv(Args[0], (char **)Args); + _exit(100); + } + close(Pipes[0]); + FILE *F = fdopen(Pipes[1], "w+"); + if (F == 0) + { + result = _error->Errno("fdopen", "Failed to open new FD"); + break; + } + + fwrite(HelloData.data(), HelloData.size(), 1, F); + fwrite("\n\n", 2, 1, F); + fflush(F); + + char *line = nullptr; + size_t linesize = 0; + ssize_t size = getline(&line, &linesize, F); + + if (size < 0) + { + _error->Error("Could not read response to hello message from hook %s: %s", Opts->Value.c_str(), strerror(errno)); + } + else if (strstr(line, "error") != nullptr) + { + _error->Error("Hook %s reported an error during hello: %s", Opts->Value.c_str(), line); + } + + size = getline(&line, &linesize, F); + if (size < 0) + { + _error->Error("Could not read message separator line after handshake from %s: %s", Opts->Value.c_str(), strerror(errno)); + } + else if (size == 0 || line[0] != '\n') + { + _error->Error("Expected empty line after handshake from %s, received %s", Opts->Value.c_str(), line); + } + + fwrite(TheData.data(), TheData.size(), 1, F); + fwrite("\n\n", 2, 1, F); + + fwrite(ByeData.data(), ByeData.size(), 1, F); + fwrite("\n\n", 2, 1, F); + fclose(F); + // Clean up the sub process + if (ExecWait(Process, Opts->Value.c_str()) == false) + { + result = _error->Error("Failure running hook %s", Opts->Value.c_str()); + break; + } + } + signal(SIGINT, old_sigint); + signal(SIGPIPE, old_sigpipe); + signal(SIGQUIT, old_sigquit); + + return result; +} diff --git a/apt-private/private-json-hooks.h b/apt-private/private-json-hooks.h new file mode 100644 index 000000000..41be2950e --- /dev/null +++ b/apt-private/private-json-hooks.h @@ -0,0 +1,14 @@ +/* + * private-json-hooks.h - 2nd generation, JSON-RPC, hooks for APT + * + * Copyright (c) 2018 Canonical Ltd + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include + +#include + +bool RunJsonHook(std::string const &option, std::string const &method, const char **FileList, CacheFile &Cache, std::set const &UnknownPackages = {}); diff --git a/apt-private/private-search.cc b/apt-private/private-search.cc index eac7abd05..2e06226d4 100644 --- a/apt-private/private-search.cc +++ b/apt-private/private-search.cc @@ -12,7 +12,9 @@ #include #include +#include #include +#include #include #include #include @@ -29,7 +31,9 @@ static bool FullTextSearch(CommandLine &CmdL) /*{{{*/ { - pkgCacheFile CacheFile; + + CacheFile CacheFile; + CacheFile.GetDepCache(); pkgCache *Cache = CacheFile.GetPkgCache(); pkgDepCache::Policy *Plcy = CacheFile.GetPolicy(); if (unlikely(Cache == NULL || Plcy == NULL)) @@ -40,6 +44,8 @@ static bool FullTextSearch(CommandLine &CmdL) /*{{{*/ if (NumPatterns < 1) return _error->Error(_("You must give at least one search pattern")); + RunJsonHook("AptCli::Hooks::Pre-Search", "org.debian.apt.hooks.pre-search", CmdL.FileList, CacheFile); + #define APT_FREE_PATTERNS() for (std::vector::iterator P = Patterns.begin(); \ P != Patterns.end(); ++P) { regfree(&(*P)); } @@ -127,6 +133,10 @@ static bool FullTextSearch(CommandLine &CmdL) /*{{{*/ for (K = output_map.begin(); K != output_map.end(); ++K) std::cout << (*K).second << std::endl; + if (output_map.empty()) + RunJsonHook("AptCli::Hooks::Fail-Search", "org.debian.apt.hooks.fail-search", CmdL.FileList, CacheFile); + else + RunJsonHook("AptCli::Hooks::Post-Search", "org.debian.apt.hooks.post-search", CmdL.FileList, CacheFile); return true; } /*}}}*/ diff --git a/debian/NEWS b/debian/NEWS index a8cd8f7ad..bf4ec8066 100644 --- a/debian/NEWS +++ b/debian/NEWS @@ -1,4 +1,12 @@ -apt (1.6~alpha8) UNRELEASED; urgency=medium +apt (1.6~rc1) UNRELEASED; urgency=medium + + Seccomp sandboxing has been turned off by default for now. If it works + for you, you are encouraged to reenable it by setting APT::Sandbox::Seccomp + to true. + + -- Julian Andres Klode Fri, 06 Apr 2018 14:14:29 +0200 + +apt (1.6~beta1) unstable; urgency=medium APT now verifies that the date of Release files is not in the future. By default, it may be 10 seconds in the future to allow for some clock drift. @@ -12,7 +20,7 @@ apt (1.6~alpha8) UNRELEASED; urgency=medium disables checks on valid-until: It is considered to mean that your machine's time is not reliable. - -- Julian Andres Klode Mon, 12 Feb 2018 12:53:18 +0100 + -- Julian Andres Klode Mon, 26 Feb 2018 13:14:13 +0100 apt (1.6~alpha1) unstable; urgency=medium diff --git a/debian/changelog b/debian/changelog index 28382e2f0..821be7c56 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,30 @@ +apt (1.6~rc1) UNRELEASED; urgency=medium + + [ Julian Andres Klode ] + * apt-pkg: Add support for zstd + * apt-inst: Add support for zstd compressed debs + * Implement compression level handling for zstd + * zstd: Implement support for multi-frame files + * Fix debian/NEWS entry for 1.6~beta1 + * Use https for Ubuntu changelogs + * Bump cache major version to allow different 1.5 and 1.6 updates + * CI: Switch testing to use ubuntu:bionic for 1.6.y + * Turn off seccomp sandboxing by default (LP: #1732030) (Closes: #890489) + * Allow restart_syscall() syscall in seccomp sandboxes (Closes: #891644) + * Delete /etc/dpkg/dpkg.cfg.d/excludes on Docker CI images + * test: export GCOV_ERROR_FILE=/dev/null to make it fail less/no tests + * apt-private: Collect not found packages in CacheSetHelperAPTGet + * Introduce experimental new hooks for command-line tools + + [ David Kalnischkies ] + * remove duplicate changelog lines from 1.6~beta1 entry + * fix communication typo in https manpage + * set our two libapt libraries to prio:optional + * mention mirror method in sources.list (Closes: 679580) + * document Acquire::AllowReleaseInfoChange without extra s + + -- Julian Andres Klode Fri, 13 Apr 2018 22:54:10 +0200 + apt (1.6~beta1) unstable; urgency=medium [ David Kalnischkies ] @@ -5,24 +32,15 @@ apt (1.6~beta1) unstable; urgency=medium * add apt-helper drop-privs command… * restore gcc visibility=hidden for apt-private * ensure correct file permissions for auxfiles - * allow the apt/lists/auxfiles/ directory to be missing (Closes: 887624) - * add apt-helper drop-privs command… - * restore gcc visibility=hidden for apt-private - * ensure correct file permissions for auxfiles [ Julian Andres Klode ] * indexcopy: Copy uncompressed indices from cdrom again (LP: #1746807) * Work around test-method-mirror failure by setting umask at start * Check that Date of Release file is not in the future * apt.conf.autoremove: Add linux-cloud-tools to list (LP: #1698159) - * indexcopy: Copy uncompressed indices from cdrom again (LP: #1746807) - * Work around test-method-mirror failure by setting umask at start - * Check that Date of Release file is not in the future - * apt.conf.autoremove: Add linux-cloud-tools to list (LP: #1698159) [ Chris Leick ] * German manpage translation update - * German manpage translation update -- Julian Andres Klode Mon, 26 Feb 2018 13:14:13 +0100 diff --git a/debian/control b/debian/control index a9063eb13..333150ff8 100644 --- a/debian/control +++ b/debian/control @@ -23,6 +23,7 @@ Build-Depends: cmake (>= 3.4), liblzma-dev, libseccomp-dev [amd64 arm64 armel armhf i386 mips mips64el mipsel ppc64el s390x hppa powerpc powerpcspe ppc64 x32], libudev-dev [linux-any], + libzstd-dev (>= 1.0), pkg-config, po4a (>= 0.34-2), xsltproc, @@ -67,6 +68,7 @@ Description: commandline package manager Package: libapt-pkg5.0 Architecture: any Multi-Arch: same +Priority: optional Pre-Depends: ${misc:Pre-Depends} Depends: ${misc:Depends}, ${shlibs:Depends} Breaks: appstream (<< 0.9.0-3~), apt (<< 1.1~exp14), libapt-inst1.5 (<< 0.9.9~) @@ -92,6 +94,7 @@ Description: package management runtime library Package: libapt-inst2.0 Architecture: any Multi-Arch: same +Priority: optional Pre-Depends: ${misc:Pre-Depends} Depends: ${misc:Depends}, ${shlibs:Depends} Section: libs diff --git a/doc/apt-get.8.xml b/doc/apt-get.8.xml index 520cfc126..3c1e90744 100644 --- a/doc/apt-get.8.xml +++ b/doc/apt-get.8.xml @@ -593,7 +593,7 @@ label, codename, suite, version and defaultpin. See also &apt-preferences;. - Configuration Item: Acquire::AllowReleaseInfoChanges. + Configuration Item: Acquire::AllowReleaseInfoChange. diff --git a/doc/apt-transport-https.1.xml b/doc/apt-transport-https.1.xml index 1bad8578b..1327b0a16 100644 --- a/doc/apt-transport-https.1.xml +++ b/doc/apt-transport-https.1.xml @@ -38,7 +38,7 @@ a user but used by APT tools based on user configuration. which, as indicated by the appended S, is wrapped in an encrypted layer known as Transport Layer Security (TLS) to provide end-to-end encryption. A sufficiently capable attacker can still observe the communication partners -and deeper analyse of the encrypted communcation might still reveal important details. +and deeper analyse of the encrypted communication might still reveal important details. An overview over available alternative transport methods is given in &sources-list;. diff --git a/doc/apt-verbatim.ent b/doc/apt-verbatim.ent index d7817cb79..a8aa0bf5e 100644 --- a/doc/apt-verbatim.ent +++ b/doc/apt-verbatim.ent @@ -93,6 +93,12 @@ " > + + apt-transport-mirror + 1 + " +> + sources.list 5 diff --git a/doc/examples/configure-index b/doc/examples/configure-index index 3763aa900..4612f362e 100644 --- a/doc/examples/configure-index +++ b/doc/examples/configure-index @@ -416,6 +416,7 @@ Dir "" dpkg-source ""; dpkg-buildpackage "/usr/bin/dpkg-buildpackage"; lz4 ""; + zstd ""; gzip ""; xz ""; bzip2 ""; diff --git a/doc/po/apt-doc.pot b/doc/po/apt-doc.pot index 036766a8c..2a9af09f4 100644 --- a/doc/po/apt-doc.pot +++ b/doc/po/apt-doc.pot @@ -1438,7 +1438,7 @@ msgid "" "codename, suite, " "version and defaultpin. See also " "&apt-preferences;. Configuration Item: " -"Acquire::AllowReleaseInfoChanges." +"Acquire::AllowReleaseInfoChange." msgstr "" #. type: Content of: @@ -7235,7 +7235,7 @@ msgid "" "&apt-transport-http;), which, as indicated by the appended S, is wrapped in " "an encrypted layer known as Transport Layer Security (TLS) to provide " "end-to-end encryption. A sufficiently capable attacker can still observe " -"the communication partners and deeper analyse of the encrypted communcation " +"the communication partners and deeper analyse of the encrypted communication " "might still reveal important details. An overview over available " "alternative transport methods is given in &sources-list;." msgstr "" diff --git a/doc/po/de.po b/doc/po/de.po index 35e1a6de1..ce9664693 100644 --- a/doc/po/de.po +++ b/doc/po/de.po @@ -2010,7 +2010,7 @@ msgid "" "certain fields like origin, label, " "codename, suite, version and defaultpin. See also &apt-preferences;. " -"Configuration Item: Acquire::AllowReleaseInfoChanges." +"Configuration Item: Acquire::AllowReleaseInfoChange." msgstr "" "Es existieren Spezialoptionen (--allow-releaseinfo-change-Feld), um Änderungen nur für bestimmte " @@ -2018,7 +2018,7 @@ msgstr "" "codename, suite, version und defaultpin zu erlauben. Siehe auch &apt-" "preferences;. Konfigurationselement: Acquire::" -"AllowReleaseInfoChanges." +"AllowReleaseInfoChange." #. type: Content of: #: apt-get.8.xml @@ -10349,7 +10349,7 @@ msgid "" "http;), which, as indicated by the appended S, is wrapped in an encrypted " "layer known as Transport Layer Security (TLS) to provide end-to-end " "encryption. A sufficiently capable attacker can still observe the " -"communication partners and deeper analyse of the encrypted communcation " +"communication partners and deeper analyse of the encrypted communication " "might still reveal important details. An overview over available " "alternative transport methods is given in &sources-list;." msgstr "" diff --git a/doc/po/es.po b/doc/po/es.po index 663ffa86e..73028c8cf 100644 --- a/doc/po/es.po +++ b/doc/po/es.po @@ -1415,7 +1415,7 @@ msgstr "" #| "directory is defined in the APT::Changelogs::Server " #| "variable (e.g. packages.debian.org/changelogs for Debian or changelogs.ubuntu.com/" +#| "\"https://changelogs.ubuntu.com/changelogs\">changelogs.ubuntu.com/" #| "changelogs for Ubuntu). By default it displays the changelog for " #| "the version that is installed. However, you can specify the same options " #| "as for the command." @@ -1430,7 +1430,7 @@ msgstr "" "pager. El nombre de servidor y el directorio base se define con la " "variable APT::Changelogs::Server (por ejemplo, packages.debian.org/changelogs para Debian o para Debian o changelogs.ubuntu.com/changelogs para Ubuntu). Por omisión, " "muestra el fichero de registro de cambios de la versión instalada. Por otra " "parte, puede definir las mismas opciones que con la orden , label, " "codename, suite, version and defaultpin. See also &apt-preferences;. " -"Configuration Item: Acquire::AllowReleaseInfoChanges." +"Configuration Item: Acquire::AllowReleaseInfoChange." msgstr "" #. type: Content of: @@ -10082,7 +10082,7 @@ msgid "" "http;), which, as indicated by the appended S, is wrapped in an encrypted " "layer known as Transport Layer Security (TLS) to provide end-to-end " "encryption. A sufficiently capable attacker can still observe the " -"communication partners and deeper analyse of the encrypted communcation " +"communication partners and deeper analyse of the encrypted communication " "might still reveal important details. An overview over available " "alternative transport methods is given in &sources-list;." msgstr "" diff --git a/doc/po/fr.po b/doc/po/fr.po index b7703c807..867391b53 100644 --- a/doc/po/fr.po +++ b/doc/po/fr.po @@ -2006,7 +2006,7 @@ msgid "" "certain fields like origin, label, " "codename, suite, version and defaultpin. See also &apt-preferences;. " -"Configuration Item: Acquire::AllowReleaseInfoChanges." +"Configuration Item: Acquire::AllowReleaseInfoChange." msgstr "" #. type: Content of: @@ -10254,7 +10254,7 @@ msgid "" "http;), which, as indicated by the appended S, is wrapped in an encrypted " "layer known as Transport Layer Security (TLS) to provide end-to-end " "encryption. A sufficiently capable attacker can still observe the " -"communication partners and deeper analyse of the encrypted communcation " +"communication partners and deeper analyse of the encrypted communication " "might still reveal important details. An overview over available " "alternative transport methods is given in &sources-list;." msgstr "" diff --git a/doc/po/it.po b/doc/po/it.po index 42c9ab3f4..3ba6c6756 100644 --- a/doc/po/it.po +++ b/doc/po/it.po @@ -2044,7 +2044,7 @@ msgid "" "certain fields like origin, label, " "codename, suite, version and defaultpin. See also &apt-preferences;. " -"Configuration Item: Acquire::AllowReleaseInfoChanges." +"Configuration Item: Acquire::AllowReleaseInfoChange." msgstr "" #. type: Content of: @@ -10239,7 +10239,7 @@ msgid "" "http;), which, as indicated by the appended S, is wrapped in an encrypted " "layer known as Transport Layer Security (TLS) to provide end-to-end " "encryption. A sufficiently capable attacker can still observe the " -"communication partners and deeper analyse of the encrypted communcation " +"communication partners and deeper analyse of the encrypted communication " "might still reveal important details. An overview over available " "alternative transport methods is given in &sources-list;." msgstr "" diff --git a/doc/po/ja.po b/doc/po/ja.po index 3666e8677..70ab3f40d 100644 --- a/doc/po/ja.po +++ b/doc/po/ja.po @@ -1979,7 +1979,7 @@ msgid "" "certain fields like origin, label, " "codename, suite, version and defaultpin. See also &apt-preferences;. " -"Configuration Item: Acquire::AllowReleaseInfoChanges." +"Configuration Item: Acquire::AllowReleaseInfoChange." msgstr "" #. type: Content of: @@ -9852,7 +9852,7 @@ msgid "" "http;), which, as indicated by the appended S, is wrapped in an encrypted " "layer known as Transport Layer Security (TLS) to provide end-to-end " "encryption. A sufficiently capable attacker can still observe the " -"communication partners and deeper analyse of the encrypted communcation " +"communication partners and deeper analyse of the encrypted communication " "might still reveal important details. An overview over available " "alternative transport methods is given in &sources-list;." msgstr "" diff --git a/doc/po/nl.po b/doc/po/nl.po index 4e869a3d5..cd27f3446 100644 --- a/doc/po/nl.po +++ b/doc/po/nl.po @@ -2077,14 +2077,14 @@ msgid "" "certain fields like origin, label, " "codename, suite, version and defaultpin. See also &apt-preferences;. " -"Configuration Item: Acquire::AllowReleaseInfoChanges." +"Configuration Item: Acquire::AllowReleaseInfoChange." msgstr "" "Er bestaan specialistische opties (--allow-releaseinfo-change-veld) om enkel veranderingen toe te staan " "voor bepaalde velden, zoals origin, label, codename, suite, " "version en defaultpin. Zie ook &apt-" -"preferences;. Configuratie-item: Acquire::AllowReleaseInfoChangesAcquire::AllowReleaseInfoChange." #. type: Content of: @@ -10475,7 +10475,7 @@ msgid "" "http;), which, as indicated by the appended S, is wrapped in an encrypted " "layer known as Transport Layer Security (TLS) to provide end-to-end " "encryption. A sufficiently capable attacker can still observe the " -"communication partners and deeper analyse of the encrypted communcation " +"communication partners and deeper analyse of the encrypted communication " "might still reveal important details. An overview over available " "alternative transport methods is given in &sources-list;." msgstr "" diff --git a/doc/po/pl.po b/doc/po/pl.po index fd89d98ea..2f111b3e2 100644 --- a/doc/po/pl.po +++ b/doc/po/pl.po @@ -1398,7 +1398,7 @@ msgstr "" #| "directory is defined in the APT::Changelogs::Server " #| "variable (e.g. packages.debian.org/changelogs for Debian or changelogs.ubuntu.com/" +#| "\"https://changelogs.ubuntu.com/changelogs\">changelogs.ubuntu.com/" #| "changelogs for Ubuntu). By default it displays the changelog for " #| "the version that is installed. However, you can specify the same options " #| "as for the command." @@ -2084,7 +2084,7 @@ msgid "" "certain fields like origin, label, " "codename, suite, version and defaultpin. See also &apt-preferences;. " -"Configuration Item: Acquire::AllowReleaseInfoChanges." +"Configuration Item: Acquire::AllowReleaseInfoChange." msgstr "" #. type: Content of: @@ -9239,7 +9239,7 @@ msgid "" "http;), which, as indicated by the appended S, is wrapped in an encrypted " "layer known as Transport Layer Security (TLS) to provide end-to-end " "encryption. A sufficiently capable attacker can still observe the " -"communication partners and deeper analyse of the encrypted communcation " +"communication partners and deeper analyse of the encrypted communication " "might still reveal important details. An overview over available " "alternative transport methods is given in &sources-list;." msgstr "" diff --git a/doc/po/pt.po b/doc/po/pt.po index 058350339..af4c0b6f9 100644 --- a/doc/po/pt.po +++ b/doc/po/pt.po @@ -1393,7 +1393,7 @@ msgstr "" #| "directory is defined in the APT::Changelogs::Server " #| "variable (e.g. packages.debian.org/changelogs for Debian or changelogs.ubuntu.com/" +#| "\"https://changelogs.ubuntu.com/changelogs\">changelogs.ubuntu.com/" #| "changelogs for Ubuntu). By default it displays the changelog for " #| "the version that is installed. However, you can specify the same options " #| "as for the command." @@ -2018,7 +2018,7 @@ msgid "" "certain fields like origin, label, " "codename, suite, version and defaultpin. See also &apt-preferences;. " -"Configuration Item: Acquire::AllowReleaseInfoChanges." +"Configuration Item: Acquire::AllowReleaseInfoChange." msgstr "" #. type: Content of: @@ -10018,7 +10018,7 @@ msgid "" "http;), which, as indicated by the appended S, is wrapped in an encrypted " "layer known as Transport Layer Security (TLS) to provide end-to-end " "encryption. A sufficiently capable attacker can still observe the " -"communication partners and deeper analyse of the encrypted communcation " +"communication partners and deeper analyse of the encrypted communication " "might still reveal important details. An overview over available " "alternative transport methods is given in &sources-list;." msgstr "" diff --git a/doc/po/pt_BR.po b/doc/po/pt_BR.po index 56bab5079..3401ed857 100644 --- a/doc/po/pt_BR.po +++ b/doc/po/pt_BR.po @@ -1410,7 +1410,7 @@ msgid "" "certain fields like origin, label, " "codename, suite, version and defaultpin. See also &apt-preferences;. " -"Configuration Item: Acquire::AllowReleaseInfoChanges." +"Configuration Item: Acquire::AllowReleaseInfoChange." msgstr "" #. type: Content of: @@ -7560,7 +7560,7 @@ msgid "" "http;), which, as indicated by the appended S, is wrapped in an encrypted " "layer known as Transport Layer Security (TLS) to provide end-to-end " "encryption. A sufficiently capable attacker can still observe the " -"communication partners and deeper analyse of the encrypted communcation " +"communication partners and deeper analyse of the encrypted communication " "might still reveal important details. An overview over available " "alternative transport methods is given in &sources-list;." msgstr "" diff --git a/doc/sources.list.5.xml b/doc/sources.list.5.xml index 75b5d94b9..e55c5b31e 100644 --- a/doc/sources.list.5.xml +++ b/doc/sources.list.5.xml @@ -409,6 +409,17 @@ deb-src [ option1=value1 option2=value2 ] uri suite [component1] [component2] [. alternative. + mirror, mirror+scheme (&apt-transport-mirror;) + + The mirror scheme specifies the location of a mirrorlist. By default the + scheme used for the location is http, but any other + scheme can be used via mirror+scheme. + The mirrorlist itself can contain many different URIs for mirrors the APT client + can transparently pick, choose and fallback between intended to help both + with distributing the load over the available mirrors and ensuring that + clients can acquire data even if some configured mirrors are not available. + + file diff --git a/methods/aptmethod.h b/methods/aptmethod.h index 331411571..cb5a30e21 100644 --- a/methods/aptmethod.h +++ b/methods/aptmethod.h @@ -106,7 +106,7 @@ protected: if (SeccompFlags == 0) return true; - if (_config->FindB("APT::Sandbox::Seccomp", true) == false) + if (_config->FindB("APT::Sandbox::Seccomp", false) == false) return true; if (RunningInQemu() == true) @@ -227,6 +227,7 @@ protected: ALLOW(rename); ALLOW(renameat); ALLOW(renameat2); + ALLOW(restart_syscall); ALLOW(rt_sigaction); ALLOW(rt_sigpending); ALLOW(rt_sigprocmask); diff --git a/prepare-release b/prepare-release index e9e9362da..a37962cd7 100755 --- a/prepare-release +++ b/prepare-release @@ -105,6 +105,12 @@ elif [ "$1" = 'pre-build' ]; then echo "You probably want to run »./prepare-release pre-export« to fix this." exit 1 fi + NEWSDISTRIBUTION=$(dpkg-parsechangelog -l debian/NEWS | sed -n -e '/^Distribution:/s/^Distribution: //p') + if [ "$NEWSDISTRIBUTION" = 'UNRELEASED' ]; then + echo "changelog (${VERSION}) has a distribution (${DISTRIBUTION}) set, while the NEWS file hasn't!" + echo "You probably want to edit »debian/NEWS« to fix this." + exit 1 + fi fi elif [ "$1" = 'post-build' ]; then if [ "$DISTRIBUTION" != "UNRELEASED" ]; then diff --git a/shippable.yml b/shippable.yml index 54a707cdd..5aafda9cf 100644 --- a/shippable.yml +++ b/shippable.yml @@ -2,8 +2,8 @@ language: none build: pre_ci_boot: - image_name: debian - image_tag: stretch + image_name: ubuntu + image_tag: bionic pull: true ci: - apt-get install -qq build-essential expect @@ -11,5 +11,6 @@ build: - mkdir build - ( cd build && cmake -DWITH_CURL=OFF .. ) - make -C build -j 4 + - rm -f /etc/dpkg/dpkg.cfg.d/excludes - CTEST_OUTPUT_ON_FAILURE=1 make -C build test - unbuffer ./test/integration/run-tests -q -j 4 diff --git a/test/integration/framework b/test/integration/framework index cf0a02de3..38a084302 100644 --- a/test/integration/framework +++ b/test/integration/framework @@ -479,6 +479,9 @@ EOF unset GREP_OPTIONS DEB_BUILD_PROFILES unset http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy + # Make gcov shut up + export GCOV_ERROR_FILE=/dev/null + # If gpgv supports --weak-digest, pass it to make sure we can disable SHA1 if aptkey verify --weak-digest SHA1 --help 2>/dev/null >/dev/null; then echo 'Acquire::gpgv::Options { "--weak-digest"; "sha1"; };' > rootdir/etc/apt/apt.conf.d/no-sha1 @@ -624,6 +627,7 @@ configcompression() { '.') printf ".\t.\tcat\n";; 'gz') printf "gzip\tgz\t$CMD $1\n";; 'bz2') printf "bzip2\tbz2\t$CMD $1\n";; + 'zst') printf "zstd\tzst\t$CMD $1\n";; *) printf "$1\t$1\t$CMD $1\n";; esac shift @@ -652,6 +656,7 @@ forcecompressor() { case $COMPRESSOR in gzip) COMPRESS='gz';; bzip2) COMPRESS='bz2';; + zstd) COMPRESS='zst';; esac local CONFFILE="${TMPWORKINGDIRECTORY}/rootdir/etc/apt/apt.conf.d/00force-compressor" echo "Acquire::CompressionTypes::Order { \"${COMPRESS}\"; }; diff --git a/test/integration/test-apt-cli-json-hooks b/test/integration/test-apt-cli-json-hooks new file mode 100755 index 000000000..5dd055430 --- /dev/null +++ b/test/integration/test-apt-cli-json-hooks @@ -0,0 +1,129 @@ +#!/bin/sh +set -e + +TESTDIR="$(readlink -f "$(dirname "$0")")" +. "$TESTDIR/framework" + +setupenvironment +configarchitecture "i386" + +DESCR='Some description that has a unusual word xxyyzz and aabbcc and a UPPERCASE' +DESCR2='Some other description with the unusual aabbcc only' +insertpackage 'unstable' 'foo' 'all' '1.0' '' '' "$DESCR + Long description of stuff and such, with lines + . + and paragraphs and everything." +insertpackage 'testing' 'bar' 'i386' '2.0' '' '' "$DESCR2" + +setupaptarchive + +APTARCHIVE="$(readlink -f ./aptarchive)" + +cat >> json-hook.sh << EOF +#!/bin/bash +while true; do + read request <&\$APT_HOOK_SOCKET + read empty <&\$APT_HOOK_SOCKET + + if echo "\$request" | grep -q ".hello"; then + echo "HOOK: HELLO" + printf '{"jsonrpc": "2.0", "result": {"version": "0.1"}, "id": 0}\n\n' >&\$APT_HOOK_SOCKET + fi + + if echo "\$request" | grep -q ".bye"; then + echo "HOOK: BYE" + exit 0; + fi + + echo HOOK: request \$request + echo HOOK: empty \$empty +done +EOF + +chmod +x json-hook.sh + +HOOK="$(readlink -f ./json-hook.sh)" + +# Setup all hooks +cat >> rootdir/etc/apt/apt.conf.d/99-json-hooks << EOF + AptCli::Hooks::Pre-Download:: "$HOOK"; + //AptCli::Hooks::Post-Download:: "$HOOK"; + //AptCli::Hooks::Fail-Download:: "$HOOK"; + //AptCli::Hooks::Pre-Install:: "$HOOK"; + AptCli::Hooks::Post-Install:: "$HOOK"; + AptCli::Hooks::Fail-Install:: "$HOOK"; + AptCli::Hooks::Pre-Search:: "$HOOK"; + AptCli::Hooks::Post-Search:: "$HOOK"; + AptCli::Hooks::Fail-Search:: "$HOOK"; +EOF + + +############################# Success search ####################### +testsuccessequal 'HOOK: HELLO +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.hello","id":0,"params":{"versions":["0.1"]}} +HOOK: empty +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.pre-search","params":{"command":"search","search-terms":["foo"],"unknown-packages":[],"packages":[]}} +HOOK: empty +HOOK: BYE +Sorting... +Full Text Search... +foo/unstable 1.0 all + Some description that has a unusual word xxyyzz and aabbcc and a UPPERCASE + +HOOK: HELLO +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.hello","id":0,"params":{"versions":["0.1"]}} +HOOK: empty +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.post-search","params":{"command":"search","search-terms":["foo"],"unknown-packages":[],"packages":[]}} +HOOK: empty +HOOK: BYE' apt search foo + +############################# Failed search ####################### +testsuccessequal 'HOOK: HELLO +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.hello","id":0,"params":{"versions":["0.1"]}} +HOOK: empty +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.pre-search","params":{"command":"search","search-terms":["foox"],"unknown-packages":[],"packages":[]}} +HOOK: empty +HOOK: BYE +Sorting... +Full Text Search... +HOOK: HELLO +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.hello","id":0,"params":{"versions":["0.1"]}} +HOOK: empty +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.fail-search","params":{"command":"search","search-terms":["foox"],"unknown-packages":[],"packages":[]}} +HOOK: empty +HOOK: BYE' apt search foox + + +############################# Failed install ####################### + +testfailureequal 'Reading package lists... +Building dependency tree... +HOOK: HELLO +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.hello","id":0,"params":{"versions":["0.1"]}} +HOOK: empty +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.fail-install","params":{"command":"install","search-terms":["foxxx"],"unknown-packages":["foxxx"],"packages":[]}} +HOOK: empty +HOOK: BYE +E: Unable to locate package foxxx' apt install foxxx + +############################# Success install ####################### + +testsuccessequal 'Reading package lists... +Building dependency tree... +HOOK: HELLO +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.hello","id":0,"params":{"versions":["0.1"]}} +HOOK: empty +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.pre-download","params":{"command":"install","search-terms":["foo"],"unknown-packages":[],"packages":[{"id":1,"name":"foo","architecture":"i386","mode":"install","automatic":false,"versions":{"candidate":{"id":1,"version":"1.0","architecture":"all","pin":500},"install":{"id":1,"version":"1.0","architecture":"all","pin":500}}}]}} +HOOK: empty +HOOK: BYE +The following NEW packages will be installed: + foo +0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. +Inst foo (1.0 unstable [all]) +Conf foo (1.0 unstable [all]) +HOOK: HELLO +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.hello","id":0,"params":{"versions":["0.1"]}} +HOOK: empty +HOOK: request {"jsonrpc":"2.0","method":"org.debian.apt.hooks.post-install","params":{"command":"install","search-terms":["foo"],"unknown-packages":[],"packages":[{"id":1,"name":"foo","architecture":"i386","mode":"install","automatic":false,"versions":{"candidate":{"id":1,"version":"1.0","architecture":"all","pin":500},"install":{"id":1,"version":"1.0","architecture":"all","pin":500}}}]}} +HOOK: empty +HOOK: BYE' apt install foo -s diff --git a/test/integration/test-apt-get-changelog b/test/integration/test-apt-get-changelog index ee7b3ef97..2a632d6db 100755 --- a/test/integration/test-apt-get-changelog +++ b/test/integration/test-apt-get-changelog @@ -27,12 +27,12 @@ releasechanger() { rm -f rootdir/var/cache/apt/*.bin } releasechanger 'Origin' 'Ubuntu' -testsuccessequal "'http://changelogs.ubuntu.com/changelogs/pool/main/f/foo/foo_1.0/changelog' foo.changelog -'http://changelogs.ubuntu.com/changelogs/pool/main/libb/libbar/libbar_1.0/changelog' libbar.changelog" aptget changelog foo libbar --print-uris +testsuccessequal "'https://changelogs.ubuntu.com/changelogs/pool/main/f/foo/foo_1.0/changelog' foo.changelog +'https://changelogs.ubuntu.com/changelogs/pool/main/libb/libbar/libbar_1.0/changelog' libbar.changelog" aptget changelog foo libbar --print-uris releasechanger 'Label' 'Debian' -testsuccessequal "'http://changelogs.ubuntu.com/changelogs/pool/main/f/foo/foo_1.0/changelog' foo.changelog -'http://changelogs.ubuntu.com/changelogs/pool/main/libb/libbar/libbar_1.0/changelog' libbar.changelog" aptget changelog foo libbar --print-uris +testsuccessequal "'https://changelogs.ubuntu.com/changelogs/pool/main/f/foo/foo_1.0/changelog' foo.changelog +'https://changelogs.ubuntu.com/changelogs/pool/main/libb/libbar/libbar_1.0/changelog' libbar.changelog" aptget changelog foo libbar --print-uris testsuccessequal "'http://localhost:${APTHTTPPORT}/main/f/foo/foo_1.0.changelog' foo.changelog 'http://localhost:${APTHTTPPORT}/main/libb/libbar/libbar_1.0.changelog' libbar.changelog" aptget changelog foo libbar --print-uris -o Acquire::Changelogs::URI::Label::Debian="http://localhost:${APTHTTPPORT}/@CHANGEPATH@.changelog" diff --git a/test/libapt/fileutl_test.cc b/test/libapt/fileutl_test.cc index a702c16ec..38536f77f 100644 --- a/test/libapt/fileutl_test.cc +++ b/test/libapt/fileutl_test.cc @@ -180,7 +180,7 @@ static void TestFileFd(mode_t const a_umask, mode_t const ExpectedFilePermission static void TestFileFd(unsigned int const filemode) { auto const compressors = APT::Configuration::getCompressors(); - EXPECT_EQ(7, compressors.size()); + EXPECT_EQ(8, compressors.size()); bool atLeastOneWasTested = false; for (auto const &c: compressors) { @@ -204,7 +204,7 @@ TEST(FileUtlTest, FileFD) _config->Set("APT::Compressor::rev::Binary", "rev"); _config->Set("APT::Compressor::rev::Cost", 10); auto const compressors = APT::Configuration::getCompressors(false); - EXPECT_EQ(7, compressors.size()); + EXPECT_EQ(8, compressors.size()); EXPECT_TRUE(std::any_of(compressors.begin(), compressors.end(), [](APT::Configuration::Compressor const &c) { return c.Name == "rev"; })); std::string const startdir = SafeGetCWD();