diff options
author | Julian Andres Klode <jak@debian.org> | 2018-04-15 19:39:31 +0000 |
---|---|---|
committer | Julian Andres Klode <jak@debian.org> | 2018-04-15 19:39:31 +0000 |
commit | 6b766aedf66b4f1394764adb5b5ba6d46ed8a200 (patch) | |
tree | 29e0c99ef9b5483a582a936e09dfdb82c078bcb0 /apt-private | |
parent | 1cbb4c95f3fdd0872a7f1cb0f970f50a68c13959 (diff) | |
parent | e9796b9c21ee7d8e8f5d6e2a24db43fc4368b557 (diff) |
Merge branch 'pu/heavy-hooks' into 'master'
json-based hooks for apt cli tools
See merge request apt-team/apt!10
Diffstat (limited to 'apt-private')
-rw-r--r-- | apt-private/private-cacheset.cc | 10 | ||||
-rw-r--r-- | apt-private/private-cacheset.h | 2 | ||||
-rw-r--r-- | apt-private/private-install.cc | 29 | ||||
-rw-r--r-- | apt-private/private-install.h | 2 | ||||
-rw-r--r-- | apt-private/private-json-hooks.cc | 425 | ||||
-rw-r--r-- | apt-private/private-json-hooks.h | 14 | ||||
-rw-r--r-- | apt-private/private-search.cc | 12 |
7 files changed, 484 insertions, 10 deletions
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<std::pair<pkgCache::VerIterator, std::string> > selectedByRelease; + std::set<std::string> notFound; explicit CacheSetHelperAPTGet(std::ostream &out); diff --git a/apt-private/private-install.cc b/apt-private/private-install.cc index b24b96351..e1beb21c6 100644 --- a/apt-private/private-install.cc +++ b/apt-private/private-install.cc @@ -35,6 +35,7 @@ #include <apt-private/private-cacheset.h> #include <apt-private/private-download.h> #include <apt-private/private-install.h> +#include <apt-private/private-json-hooks.h> #include <apt-private/private-output.h> #include <apti18n.h> @@ -569,10 +570,11 @@ bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, CacheFile &Cache, int bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector<std::string> &VolatileCmdL, CacheFile &Cache, int UpgradeMode) { std::map<unsigned short, APT::VersionSet> verset; - return DoCacheManipulationFromCommandLine(CmdL, VolatileCmdL, Cache, verset, UpgradeMode); + std::set<std::string> UnknownPackages; + return DoCacheManipulationFromCommandLine(CmdL, VolatileCmdL, Cache, verset, UpgradeMode, UnknownPackages); } bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector<std::string> &VolatileCmdL, CacheFile &Cache, - std::map<unsigned short, APT::VersionSet> &verset, int UpgradeMode) + std::map<unsigned short, APT::VersionSet> &verset, int UpgradeMode, std::set<std::string> &UnknownPackages) { // Enter the special broken fixing mode if the user specified arguments bool BrokenFix = false; @@ -621,6 +623,8 @@ bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector<std::stri APT::VersionContainerInterface::FromPackage(&(verset[MOD_INSTALL]), Cache, P, APT::CacheSetHelper::CANDIDATE, helper); } + UnknownPackages = helper.notFound; + if (_error->PendingError() == true) { helper.showVirtualPackageErrors(Cache); @@ -726,8 +730,13 @@ bool DoInstall(CommandLine &CmdL) return false; std::map<unsigned short, APT::VersionSet> verset; - if(!DoCacheManipulationFromCommandLine(CmdL, VolatileCmdL, Cache, verset, 0)) + std::set<std::string> UnknownPackages; + + if (!DoCacheManipulationFromCommandLine(CmdL, VolatileCmdL, Cache, verset, 0, UnknownPackages)) + { + RunJsonHook("AptCli::Hooks::Install", "org.debian.apt.hooks.install.fail", CmdL.FileList, Cache, UnknownPackages); return false; + } /* Print out a list of packages that are going to be installed extra to what the user asked */ @@ -828,12 +837,22 @@ bool DoInstall(CommandLine &CmdL) always_true, string_ident, verbose_show_candidate); } + RunJsonHook("AptCli::Hooks::Install", "org.debian.apt.hooks.install.pre-prompt", CmdL.FileList, Cache); + + bool result; // See if we need to prompt // FIXME: check if really the packages in the set are going to be installed if (Cache->InstCount() == verset[MOD_INSTALL].size() && Cache->DelCount() == 0) - return InstallPackages(Cache,false,false); + result = InstallPackages(Cache, false, false); + else + result = InstallPackages(Cache, false); + + if (result) + result = RunJsonHook("AptCli::Hooks::Install", "org.debian.apt.hooks.install.post", CmdL.FileList, Cache); + else + /* not a result */ RunJsonHook("AptCli::Hooks::Install", "org.debian.apt.hooks.install.fail", CmdL.FileList, Cache); - return 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<std::string> &VolatileCmdL, CacheFile &Cache, - std::map<unsigned short, APT::VersionSet> &verset, int UpgradeMode); + std::map<unsigned short, APT::VersionSet> &verset, int UpgradeMode, std::set<std::string> &UnknownPackages); bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vector<std::string> &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 <apt-pkg/debsystem.h> +#include <apt-pkg/macros.h> +#include <apt-private/private-json-hooks.h> + +#include <ostream> +#include <sstream> +#include <stack> + +#include <signal.h> +#include <sys/socket.h> +#include <sys/types.h> + +/** + * @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<write_state> old_states; + + void maybeComma() + { + switch (state) + { + case empty: + break; + case in_object_val: + state = in_object_key; + break; + case in_object_key: + state = in_object_val; + os << ','; + break; + case in_array: + os << ','; + break; + case in_array_first_element: + state = in_array; + break; + case in_object_first_key: + state = in_object_val; + break; + default: + abort(); + } + } + + void pushState(write_state state) + { + old_states.push(this->state); + this->state = state; + } + + void popState() + { + this->state = old_states.top(); + } + + 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<std::string> 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<std::string> 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<int> KeepFDs; + MergeKeepFdsFromConfiguration(KeepFDs); + int Pipes[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, Pipes) != 0) + { + result = _error->Errno("pipe", "Failed to create IPC pipe to subprocess"); + break; + } + + int InfoFD = 3; + + if (InfoFD != Pipes[0]) + SetCloseExec(Pipes[0], true); + else + KeepFDs.insert(Pipes[0]); + + SetCloseExec(Pipes[1], true); + + // Purified Fork for running the script + pid_t Process = ExecFork(KeepFDs); + if (Process == 0) + { + // Setup the FDs + dup2(Pipes[0], InfoFD); + SetCloseExec(STDOUT_FILENO, false); + SetCloseExec(STDIN_FILENO, false); + SetCloseExec(STDERR_FILENO, false); + + 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 <set> +#include <string> + +#include <apt-private/private-cachefile.h> + +bool RunJsonHook(std::string const &option, std::string const &method, const char **FileList, CacheFile &Cache, std::set<std::string> const &UnknownPackages = {}); diff --git a/apt-private/private-search.cc b/apt-private/private-search.cc index eac7abd05..52a52e522 100644 --- a/apt-private/private-search.cc +++ b/apt-private/private-search.cc @@ -12,7 +12,9 @@ #include <apt-pkg/policy.h> #include <apt-pkg/progress.h> +#include <apt-private/private-cachefile.h> #include <apt-private/private-cacheset.h> +#include <apt-private/private-json-hooks.h> #include <apt-private/private-output.h> #include <apt-private/private-search.h> #include <apt-private/private-show.h> @@ -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::Search", "org.debian.apt.hooks.search.pre", CmdL.FileList, CacheFile); + #define APT_FREE_PATTERNS() for (std::vector<regex_t>::iterator P = Patterns.begin(); \ P != Patterns.end(); ++P) { regfree(&(*P)); } @@ -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::Search", "org.debian.apt.hooks.search.fail", CmdL.FileList, CacheFile); + else + RunJsonHook("AptCli::Hooks::Search", "org.debian.apt.hooks.search.post", CmdL.FileList, CacheFile); return true; } /*}}}*/ |