From 385d9f2f23057bc5808b5e013e77ba16d1c94da4 Mon Sep 17 00:00:00 2001 From: David Kalnischkies Date: Tue, 7 Jun 2016 17:01:33 +0200 Subject: [PATCH] edsp: optionally store a compressed copy of the last scenario For bugreports and co it could be handy to have the scenario and all the settings used in it around later for inspection for EDSP like protocols. EDSP might not be the most interesting as the user can still interrupt the process before the solution is applied and users tend to have an opinion on the "rightness" of a solution, so it is disabled by default. --- apt-pkg/edsp.cc | 42 +++- apt-pkg/edsp/edspsystem.cc | 3 + apt-pkg/init.cc | 4 +- apt-private/private-cmndline.cc | 20 +- apt-private/private-cmndline.h | 1 + apt-private/private-main.cc | 1 + cmdline/apt-dump-solver.cc | 215 +++++++++++++----- cmdline/apt-internal-solver.cc | 1 + cmdline/makefile | 4 +- .../test-external-dependency-solver-protocol | 19 +- 10 files changed, 232 insertions(+), 78 deletions(-) diff --git a/apt-pkg/edsp.cc b/apt-pkg/edsp.cc index 94cac4eb1..58d2769f9 100644 --- a/apt-pkg/edsp.cc +++ b/apt-pkg/edsp.cc @@ -597,10 +597,12 @@ bool EDSP::WriteRequest(pkgDepCache &Cache, FileFd &output, 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(_config->Find("APT::Solver", "internal")).append("::Preferences"); + solverpref.append(solver).append("::Preferences"); if (_config->Exists(solverpref) == true) WriteOkay(Okay, output, "Preferences: ", _config->Find(solverpref,""), "\n"); return WriteOkay(Okay, output, "\n"); @@ -926,15 +928,23 @@ bool EDSP::WriteError(char const * const uuid, std::string const &message, FileF "\n\n"); } /*}}}*/ +static std::string findExecutable(std::vector 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) {/*{{{*/ - std::vector const solverDirs = _config->FindVector(configdir); - std::string file; - for (std::vector::const_iterator dir = solverDirs.begin(); - dir != solverDirs.end(); ++dir) { - file = flCombine(*dir, binary); - if (RealFileExists(file.c_str()) == true) - break; - file.clear(); + 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) @@ -955,8 +965,18 @@ static pid_t ExecuteExternal(char const* const type, char const * const binary, if (Solver == 0) { dup2(external[0], STDIN_FILENO); dup2(external[3], STDOUT_FILENO); - const char* calling[2] = { file.c_str(), 0 }; - execv(calling[0], (char**) calling); + auto const dumpfile = _config->FindFile((std::string("Dir::Log::") + type).c_str()); + auto const dumpdir = flNotFile(dumpfile); + if (dumper.empty() || dumpfile.empty() || dumper == file || CreateAPTDirectoryIfNeeded(dumpdir, dumpdir) == false) + { + char const * const calling[] = { file.c_str(), nullptr }; + execv(calling[0], const_cast(calling)); + } + else + { + char const * const calling[] = { dumper.c_str(), dumpfile.c_str(), file.c_str(), nullptr }; + execv(calling[0], const_cast(calling)); + } std::cerr << "Failed to execute " << type << " '" << binary << "'!" << std::endl; _exit(100); } diff --git a/apt-pkg/edsp/edspsystem.cc b/apt-pkg/edsp/edspsystem.cc index 9b23dc3ca..2a78efe58 100644 --- a/apt-pkg/edsp/edspsystem.cc +++ b/apt-pkg/edsp/edspsystem.cc @@ -60,9 +60,12 @@ pkgPackageManager *edspLikeSystem::CreatePM(pkgDepCache * /*Cache*/) const // 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", ""); diff --git a/apt-pkg/init.cc b/apt-pkg/init.cc index 0dfb10978..a41d604d3 100644 --- a/apt-pkg/init.cc +++ b/apt-pkg/init.cc @@ -56,7 +56,7 @@ bool pkgInitConfig(Configuration &Cnf) 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","etc/apt/"); Cnf.CndSet("Dir::Etc::sourcelist","sources.list"); @@ -72,7 +72,7 @@ bool pkgInitConfig(Configuration &Cnf) Cnf.CndSet("Dir::Bin::solvers::","/usr/lib/apt/solvers"); Cnf.CndSet("Dir::Media::MountPath","/media/apt"); - // State + // State Cnf.CndSet("Dir::Log","var/log/apt"); Cnf.CndSet("Dir::Log::Terminal","term.log"); Cnf.CndSet("Dir::Log::History","history.log"); diff --git a/apt-private/private-cmndline.cc b/apt-private/private-cmndline.cc index 7e50b1401..135ee3c4e 100644 --- a/apt-private/private-cmndline.cc +++ b/apt-private/private-cmndline.cc @@ -128,6 +128,11 @@ static bool addArgumentsAPTConfig(std::vector &Args, char con else return false; + return true; +} + /*}}}*/ +static bool addArgumentsAPTDumpSolver(std::vector &, char const * const)/*{{{*/ +{ return true; } /*}}}*/ @@ -347,6 +352,7 @@ std::vector getCommandArgs(APT_CMD const Program, char const case APT_CMD::APT_CACHE: addArgumentsAPTCache(Args, Cmd); break; case APT_CMD::APT_CDROM: addArgumentsAPTCDROM(Args, Cmd); break; case APT_CMD::APT_CONFIG: addArgumentsAPTConfig(Args, Cmd); break; + case APT_CMD::APT_DUMP_SOLVER: addArgumentsAPTDumpSolver(Args, Cmd); break; case APT_CMD::APT_EXTRACTTEMPLATES: addArgumentsAPTExtractTemplates(Args, Cmd); break; case APT_CMD::APT_FTPARCHIVE: addArgumentsAPTFTPArchive(Args, Cmd); break; case APT_CMD::APT_HELPER: addArgumentsAPTHelper(Args, Cmd); break; @@ -402,6 +408,7 @@ static bool ShowCommonHelp(APT_CMD const Binary, CommandLine &CmdL, std::vector< case APT_CMD::APT_CACHE: cmd = "apt-cache(8)"; break; case APT_CMD::APT_CDROM: cmd = "apt-cdrom(8)"; break; case APT_CMD::APT_CONFIG: cmd = "apt-config(8)"; break; + case APT_CMD::APT_DUMP_SOLVER: cmd = nullptr; break; case APT_CMD::APT_EXTRACTTEMPLATES: cmd = "apt-extracttemplates(1)"; break; case APT_CMD::APT_FTPARCHIVE: cmd = "apt-ftparchive(1)"; break; case APT_CMD::APT_GET: cmd = "apt-get(8)"; break; @@ -412,14 +419,15 @@ static bool ShowCommonHelp(APT_CMD const Binary, CommandLine &CmdL, std::vector< } if (cmd != nullptr) ioprintf(std::cout, _("See %s for more information about the available commands."), cmd); - std::cout << std::endl << - _("Configuration options and syntax is detailed in apt.conf(5).\n" - "Information about how to configure sources can be found in sources.list(5).\n" - "Package and version choices can be expressed via apt_preferences(5).\n" - "Security details are available in apt-secure(8).\n"); + if (Binary != APT_CMD::APT_DUMP_SOLVER && Binary != APT_CMD::APT_INTERNAL_SOLVER) + std::cout << std::endl << + _("Configuration options and syntax is detailed in apt.conf(5).\n" + "Information about how to configure sources can be found in sources.list(5).\n" + "Package and version choices can be expressed via apt_preferences(5).\n" + "Security details are available in apt-secure(8).\n"); if (Binary == APT_CMD::APT_GET || Binary == APT_CMD::APT) std::cout << std::right << std::setw(70) << _("This APT has Super Cow Powers.") << std::endl; - else if (Binary == APT_CMD::APT_HELPER) + else if (Binary == APT_CMD::APT_HELPER || Binary == APT_CMD::APT_DUMP_SOLVER) std::cout << std::right << std::setw(70) << _("This APT helper has Super Meep Powers.") << std::endl; return true; } diff --git a/apt-private/private-cmndline.h b/apt-private/private-cmndline.h index 6235ef9f5..c0c5a7455 100644 --- a/apt-private/private-cmndline.h +++ b/apt-private/private-cmndline.h @@ -21,6 +21,7 @@ enum class APT_CMD { APT_INTERNAL_SOLVER, APT_MARK, APT_SORTPKG, + APT_DUMP_SOLVER, }; struct aptDispatchWithHelp { diff --git a/apt-private/private-main.cc b/apt-private/private-main.cc index d6517dd2a..5a5940b7e 100644 --- a/apt-private/private-main.cc +++ b/apt-private/private-main.cc @@ -34,6 +34,7 @@ void InitLocale(APT_CMD const binary) /*{{{*/ case APT_CMD::APT_MARK: textdomain("apt"); break; + case APT_CMD::APT_DUMP_SOLVER: case APT_CMD::APT_EXTRACTTEMPLATES: case APT_CMD::APT_FTPARCHIVE: case APT_CMD::APT_INTERNAL_SOLVER: diff --git a/cmdline/apt-dump-solver.cc b/cmdline/apt-dump-solver.cc index 0de248e75..c6d98cd97 100644 --- a/cmdline/apt-dump-solver.cc +++ b/cmdline/apt-dump-solver.cc @@ -7,70 +7,177 @@ ##################################################################### */ /*}}}*/ // Include Files /*{{{*/ +#include + +#include +#include #include +#include +#include + +#include -#include -#include #include #include +#include #include -#include +#include +#include + +#include +#include + +#include /*}}}*/ -// ShowHelp - Show a help screen /*{{{*/ -// --------------------------------------------------------------------- -/* */ -static bool ShowHelp() { - ioprintf(std::cout, "%s %s (%s)\n", PACKAGE, PACKAGE_VERSION, COMMON_ARCH); - std::cout << - "Usage: apt-dump-solver\n" - "\n" - "apt-dump-solver is a dummy solver who just dumps its input to the\n" - "file specified in the environment variable APT_EDSP_DUMP_FILENAME and\n" - "exists with a proper EDSP error.\n" - "\n" - " This dump has lost Super Cow Powers.\n"; - return true; +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt-dump-solver\n" + "\n" + "apt-dump-solver is an interface to store an EDSP scenario in\n" + "a file and optionally forwards it to another solver.\n"); + return true; +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return {}; +} + /*}}}*/ +static int WriteError(char const * const uid, std::ostringstream &out, FileFd &stdoutfd, pid_t const &Solver)/*{{{*/ +{ + _error->DumpErrors(out); + // ensure the solver isn't printing into "our" error message, too + if (Solver != 0) + ExecWait(Solver, "dump", true); + EDSP::WriteError(uid, out.str(), stdoutfd); + return 0; } /*}}}*/ int main(int argc,const char *argv[]) /*{{{*/ { - // we really don't need anything - DropPrivileges(); - - if (argc > 1 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1],"-h") == 0 || - strcmp(argv[1],"-v") == 0 || strcmp(argv[1],"--version") == 0)) { - ShowHelp(); - return 0; - } - - FileFd stdoutfd; - if (stdoutfd.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) - return 1; - - char const * const filename = getenv("APT_EDSP_DUMP_FILENAME"); - if (filename == NULL || strlen(filename) == 0) - { - EDSP::WriteError("ERR_NO_FILENAME", "You have to set the environment variable APT_EDSP_DUMP_FILENAME\n" - "to a valid filename to store the dump of EDSP solver input in.\n" - "For example with: export APT_EDSP_DUMP_FILENAME=/tmp/dump.edsp", stdoutfd); - return 0; - } - - RemoveFile(argv[0], filename); - FileFd input, output; - if (input.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false || - output.Open(filename, FileFd::WriteOnly | FileFd::Create | FileFd::Exclusive, FileFd::Extension, 0600) == false || - CopyFile(input, output) == false || input.Close() == false || output.Close() == false) - { - std::ostringstream out; - out << "Writing EDSP solver input to file '" << filename << "' failed!\n"; - _error->DumpErrors(out); - EDSP::WriteError("ERR_WRITE_ERROR", out.str(), stdoutfd); - return 0; - } - - EDSP::WriteError("ERR_JUST_DUMPING", "I am too dumb, i can just dump!\nPlease use one of my friends instead!", stdoutfd); - return 0; + CommandLine CmdL; + ParseCommandLine(CmdL, APT_CMD::APT_DUMP_SOLVER, &_config, nullptr, argc, argv, &ShowHelp, &GetCommands); + _config->Clear("Dir::Log"); + + bool const is_forwarding_dumper = (CmdL.FileSize() != 0); + + FileFd stdoutfd; + if (stdoutfd.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) + return 252; + + FileFd dump; + char const * const filename = is_forwarding_dumper ? CmdL.FileList[0] : getenv("APT_EDSP_DUMP_FILENAME"); + if (filename == nullptr || strlen(filename) == 0) + { + if (is_forwarding_dumper == false) + { + EDSP::WriteError("ERR_NO_FILENAME", "You have to set the environment variable APT_EDSP_DUMP_FILENAME\n" + "to a valid filename to store the dump of EDSP solver input in.\n" + "For example with: export APT_EDSP_DUMP_FILENAME=/tmp/dump.edsp", stdoutfd); + return 0; + } + } + else + { + // ignore errors here as logging isn't really critical + _error->PushToStack(); + if (dump.Open(filename, FileFd::WriteOnly | FileFd::Exclusive | FileFd::Create, FileFd::Extension, 0644) == false && + is_forwarding_dumper == false) + { + _error->MergeWithStack(); + std::ostringstream out; + out << "Writing EDSP solver input to file '" << filename << "' failed as it couldn't be created!\n"; + return WriteError("ERR_CREATE_FILE", out, stdoutfd, 0); + } + _error->RevertToStack(); + } + + pid_t Solver = 0; + FileFd forward; + if (is_forwarding_dumper) + { + int external[] = {-1, -1}; + if (pipe(external) != 0) + return 250; + for (int i = 0; i < 2; ++i) + SetCloseExec(external[i], true); + + Solver = ExecFork(); + if (Solver == 0) { + dup2(external[0], STDIN_FILENO); + execv(CmdL.FileList[1], const_cast(CmdL.FileList + 1)); + std::cerr << "Failed to execute '" << CmdL.FileList[1] << "'!" << std::endl; + _exit(100); + } + close(external[0]); + + if (WaitFd(external[1], true, 5) == false) + return 251; + + if (forward.OpenDescriptor(external[1], FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) + return 252; + } + + DropPrivileges(); + + FileFd input; + if (input.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false) + { + std::ostringstream out; + out << "Writing EDSP solver input to file '" << filename << "' failed as stdin couldn't be opened!\n"; + return WriteError("ERR_READ_ERROR", out, stdoutfd, Solver); + } + + constexpr size_t BufSize = 64 * 1024; + std::unique_ptr Buf(new char[BufSize]); + unsigned long long ToRead = 0; + do { + if (input.Read(Buf.get(),BufSize, &ToRead) == false) + { + std::ostringstream out; + out << "Writing EDSP solver input to file '" << filename << "' failed as reading from stdin failed!\n"; + return WriteError("ERR_READ_ERROR", out, stdoutfd, Solver); + } + if (ToRead == 0) + break; + if (forward.IsOpen() && forward.Failed() == false && forward.Write(Buf.get(),ToRead) == false) + forward.Close(); + if (dump.IsOpen() && dump.Failed() == false && dump.Write(Buf.get(),ToRead) == false) + dump.Close(); + } while (true); + input.Close(); + forward.Close(); + dump.Close(); + + if (_error->PendingError()) + { + std::ostringstream out; + out << "Writing EDSP solver input to file '" << filename << "' failed due to write errors!\n"; + return WriteError("ERR_WRITE_ERROR", out, stdoutfd, Solver); + } + + if (is_forwarding_dumper) + { + // Wait and collect the error code + int Status; + while (waitpid(Solver, &Status, 0) != Solver) + { + if (errno == EINTR) + continue; + + std::ostringstream out; + ioprintf(out, _("Waited for %s but it wasn't there"), CmdL.FileList[1]); + return WriteError("ERR_FORWARD", out, stdoutfd, 0); + } + if (WIFEXITED(Status)) + return WEXITSTATUS(Status); + else + return 255; + } + else + EDSP::WriteError("ERR_JUST_DUMPING", "I am too dumb, i can just dump!\nPlease use one of my friends instead!", stdoutfd); + return 0; } diff --git a/cmdline/apt-internal-solver.cc b/cmdline/apt-internal-solver.cc index 8296e8d01..aecb0eaba 100644 --- a/cmdline/apt-internal-solver.cc +++ b/cmdline/apt-internal-solver.cc @@ -123,6 +123,7 @@ int main(int argc,const char *argv[]) /*{{{*/ _config->Set("APT::System", "Debian APT solver interface"); _config->Set("APT::Solver", "internal"); _config->Set("edsp::scenario", "/nonexistent/stdin"); + _config->Clear("Dir::Log"); FileFd output; if (output.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) DIE("stdout couldn't be opened"); diff --git a/cmdline/makefile b/cmdline/makefile index 6d21b0803..cd8ff8252 100644 --- a/cmdline/makefile +++ b/cmdline/makefile @@ -88,8 +88,8 @@ include $(PROGRAM_H) # This just dumps out the state PROGRAM=apt-dump-solver -SLIBS = -lapt-pkg $(INTLLIBS) -LIB_MAKES = apt-pkg/makefile +SLIBS = -lapt-pkg -lapt-private $(INTLLIBS) +LIB_MAKES = apt-pkg/makefile apt-private/makefile SOURCE = apt-dump-solver.cc include $(PROGRAM_H) diff --git a/test/integration/test-external-dependency-solver-protocol b/test/integration/test-external-dependency-solver-protocol index 4b13edc09..10b07e896 100755 --- a/test/integration/test-external-dependency-solver-protocol +++ b/test/integration/test-external-dependency-solver-protocol @@ -25,11 +25,17 @@ insertpackage 'experimental' 'coolstuff' 'i386,amd64' '3' 'Depends: cool, stuff' setupaptarchive +testsuccess aptget install --solver apt coolstuff -s +testempty find -name 'edsp.last.*' +echo 'Dir::Log::Solver "edsp.last.xz";' > rootdir/etc/apt/apt.conf.d/log-edsp.conf + testfailure aptget install --solver dump coolstuff -s -testsuccess grep ERR_NO_FILENAME rootdir/tmp/testfailure.output +testsuccess grep 'ERR_NO_FILENAME' rootdir/tmp/testfailure.output +testfailure test -s rootdir/var/log/apt/edsp.last.xz export APT_EDSP_DUMP_FILENAME="/nonexistent/apt/edsp.dump" testfailure aptget install --solver dump coolstuff -s -testsuccess grep ERR_WRITE_ERROR rootdir/tmp/testfailure.output +testsuccess grep 'ERR_CREATE_FILE' rootdir/tmp/testfailure.output +testfailure test -s rootdir/var/log/apt/edsp.last.xz export APT_EDSP_DUMP_FILENAME="${TMPWORKINGDIRECTORY}/downloaded/dump.edsp" testfailureequal 'Reading package lists... @@ -41,8 +47,8 @@ I am too dumb, i can just dump! Please use one of my friends instead! E: External solver failed with: I am too dumb, i can just dump!' aptget install --solver dump coolstuff -s +testfailure test -s rootdir/var/log/apt/edsp.last.xz testsuccess test -s "$APT_EDSP_DUMP_FILENAME" -rm -f "$APT_EDSP_DUMP_FILENAME" testsuccessequal 'Reading package lists... Building dependency tree... @@ -52,6 +58,12 @@ The following NEW packages will be installed: 0 upgraded, 1 newly installed, 0 to remove and 2 not upgraded. Inst coolstuff (2 unstable [amd64]) Conf coolstuff (2 unstable [amd64])' aptget install --solver apt coolstuff -s +testsuccess test -s rootdir/var/log/apt/edsp.last.xz +sed -i -e 's#^Solver: dump$#Solver: apt#' "$APT_EDSP_DUMP_FILENAME" +testequal "$(cat "$APT_EDSP_DUMP_FILENAME") +" apthelper cat-file rootdir/var/log/apt/edsp.last.xz +cp rootdir/var/log/apt/edsp.last.xz rootdir/var/log/apt/edsp.last.xz.1 +rm -f "$APT_EDSP_DUMP_FILENAME" testsuccessequal 'Reading package lists... Building dependency tree... @@ -61,6 +73,7 @@ The following NEW packages will be installed: 0 upgraded, 1 newly installed, 0 to remove and 2 not upgraded. Inst coolstuff (3 experimental [amd64]) Conf coolstuff (3 experimental [amd64])' aptget install --solver apt coolstuff -s -t experimental +testfailure cmp rootdir/var/log/apt/edsp.last.xz rootdir/var/log/apt/edsp.last.xz.1 testsuccessequal "Reading package lists... Building dependency tree... -- 2.45.2