diff options
| author | Simon Johnsson <simon.johnsson@canonical.com> | 2025-09-26 18:18:42 +0200 |
|---|---|---|
| committer | Julian Andres Klode <jak@debian.org> | 2025-09-26 16:18:42 +0000 |
| commit | 66962bee233b617e4a9ce436f9b26875511b35e6 (patch) | |
| tree | eb72d44c499404fd46c04119e7e5fb214f7833f2 /apt-pkg | |
| parent | 3c9399e643a07074d47c9bceca88e8d43ff55d36 (diff) | |
History Command and Parsing
Diffstat (limited to 'apt-pkg')
| -rw-r--r-- | apt-pkg/history.cc | 190 | ||||
| -rw-r--r-- | apt-pkg/history.h | 80 |
2 files changed, 270 insertions, 0 deletions
diff --git a/apt-pkg/history.cc b/apt-pkg/history.cc new file mode 100644 index 000000000..4e838a016 --- /dev/null +++ b/apt-pkg/history.cc @@ -0,0 +1,190 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + Set of functions for parsing the history log + ##################################################################### */ +/*}}}*/ +// Include Files /*{{{*/ + +#include <config.h> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/history.h> +#include <apt-pkg/strutl.h> +#include <apt-pkg/tagfile.h> + +#include <algorithm> +#include <cctype> + +#include <glob.h> +#include <apti18n.h> // for coloring + +namespace APT::History +{ + +static Change ParsePackageEvent(const std::string &event) +{ + Change change{}; + // Remove all spaces + std::string trimEvent = event; + trimEvent.erase(std::remove_if(trimEvent.begin(), trimEvent.end(), + [](unsigned char c) + { return std::isspace(c); }), + trimEvent.end()); + auto openParen = trimEvent.find('('); + if (openParen == std::string::npos) + return change; + + change.package = trimEvent.substr(0, openParen); + + auto closeParen = trimEvent.find(')', openParen); + if (closeParen == std::string::npos) + return change; + + std::string versionStr = trimEvent.substr(openParen + 1, closeParen - openParen - 1); + auto values = VectorizeString(versionStr, ','); + for (size_t i = 0; i < values.size(); ++i) + { + if (i == 0 && std::isdigit(values[0][0])) + change.currentVersion = std::move(values[i]); + else if (i == 1 && std::isdigit(values[1][0])) + change.candidateVersion = std::move(values[i]); + else if (values[i] == "automatic") + change.automatic = true; + else + _error->Warning(_("Unknown flag: %s"), values[i].c_str()); + } + return change; +} + +static std::vector<std::string> SplitPackagesInContent(const std::string &content) +{ + std::vector<std::string> result; + std::size_t start = 0; + + if (content.length() == 0) + return result; + + // Split at "), " but keep the parenthesis. + while (true) + { + std::size_t pos = content.find("), ", start); + if (pos == std::string::npos) + { + result.push_back(content.substr(start)); + break; + } + result.push_back(content.substr(start, pos - start + 1)); + // Increment by 3 since 3 == len("), ") + start = pos + 3; + } + return result; +} + +std::string KindToString(const Kind &kind) +{ + switch (kind) + { + case Kind::Install: + return "Install"; + case Kind::Reinstall: + return "Reinstall"; + case Kind::Upgrade: + return "Upgrade"; + case Kind::Downgrade: + return "Downgrade"; + case Kind::Remove: + return "Remove"; + case Kind::Purge: + return "Purge"; + default: + return "Undefined"; + } +} + +Entry ParseSection( + const pkgTagSection §ion) +{ + Entry entry{}; + entry.startDate = section.Find("Start-Date"); + entry.endDate = section.Find("End-Date"); + entry.cmdLine = section.Find("Commandline"); + entry.requestingUser = section.Find("Requested-By"); + entry.comment = section.Find("Comment"); + entry.error = section.Find("Error"); + + std::string content = ""; + const Kind kinds[] = + { + Kind::Install, + Kind::Reinstall, + Kind::Downgrade, + Kind::Upgrade, + Kind::Remove, + Kind::Purge, + }; + for (const auto &kind : kinds) + { + content = section.Find(KindToString(kind)); + if (content.empty()) + continue; + + std::vector<std::string> package_events = SplitPackagesInContent(content); + std::vector<Change> changes = {}; + for (auto event : package_events) + { + Change change = ParsePackageEvent(event); + change.kind = kind; + changes.push_back(change); + } + // Changed packages should be in order + std::sort(changes.begin(), changes.end(), [](const Change &a, const Change &b) + { return a.package < b.package; }); + entry.changeMap[kind] = changes; + } + + return entry; +} + +bool ParseFile(FileFd &fd, HistoryBuffer &buf) +{ + pkgTagFile file(&fd, FileFd::ReadOnly); + pkgTagSection tmpSection; + while (file.Step(tmpSection)) + buf.push_back(ParseSection(tmpSection)); + return true; +} + +bool ParseLogDir(HistoryBuffer &buf) +{ + std::string files = _config->FindFile("Dir::Log::History") + "*"; + const char *pattern = files.data(); + glob_t result; + + int ret = glob(pattern, GLOB_TILDE, nullptr, &result); + if (ret != 0) + return _error->Error(_("Cannot find history files: %s"), files.c_str()); + + for (size_t i = 0; i < result.gl_pathc; ++i) + { + FileFd fd; + if (not fd.Open(result.gl_pathv[i], FileFd::ReadOnly, FileFd::Extension)) + return _error->Error(_("Could not open file: %s"), result.gl_pathv[i]); + if (not ParseFile(fd, buf)) + return _error->Error(_("Could not parse file: %s"), result.gl_pathv[i]); + if (not fd.Close()) + return _error->Error(_("Could not close file: %s"), result.gl_pathv[i]); + } + + // Sort entries by time + std::sort(buf.begin(), buf.end(), + [](const Entry &a, const Entry &b) + { + return a.startDate < b.startDate; + }); + + return true; +} +} // namespace APT::History diff --git a/apt-pkg/history.h b/apt-pkg/history.h new file mode 100644 index 000000000..08e7be388 --- /dev/null +++ b/apt-pkg/history.h @@ -0,0 +1,80 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + History - Parse history logs to structured data. + + ##################################################################### */ +/*}}}*/ +#ifndef APTPKG_HISTORY_H +#define APTPKG_HISTORY_H + +#include <apt-pkg/fileutl.h> +#include <apt-pkg/macros.h> +#include <apt-pkg/tagfile.h> + +#include <map> +#include <string> +#include <vector> + +namespace APT::History +{ + +enum class Kind +{ + Install, + Reinstall, + Upgrade, + Downgrade, + Remove, + Purge, +}; + +// KindToString - Take a kind and return its +// string representation. +// +// NOTE: Non-localized English version +std::string KindToString(const Kind &kind); + +struct Change +{ + Kind kind; + std::string package; + std::string currentVersion; + std::string candidateVersion; + bool automatic = false; + + private: + void *d; // pointer for future extension; +}; + +struct Entry +{ + // Strings instead of string_view to avoid reference errors + std::string startDate; + std::string endDate; + std::string cmdLine; + std::string comment; + std::string error; + std::string requestingUser; + std::map<Kind, std::vector<Change>> changeMap; + + private: + void *d; +}; + +// History is defined as the collection of entries in the history log(s). +typedef std::vector<Entry> HistoryBuffer; + +// ParseSection - Take a tag section and parse it as a +// history log entry. +APT_PUBLIC Entry ParseSection(const pkgTagSection §ion); +// ParseFile - Take a file descriptor and parse it as a history +// log to the given buffer. +// NOTE: Caller is responsible for closing the file descriptor. +APT_PUBLIC bool ParseFile(FileFd &fd, HistoryBuffer &buf); +// ParseLogDir - Parse the apt history log directory to the buffer. +APT_PUBLIC bool ParseLogDir(HistoryBuffer &buf); +} // namespace APT::History + +#endif |
