summaryrefslogtreecommitdiff
path: root/apt-pkg
diff options
context:
space:
mode:
authorSimon Johnsson <simon.johnsson@canonical.com>2025-09-26 18:18:42 +0200
committerJulian Andres Klode <jak@debian.org>2025-09-26 16:18:42 +0000
commit66962bee233b617e4a9ce436f9b26875511b35e6 (patch)
treeeb72d44c499404fd46c04119e7e5fb214f7833f2 /apt-pkg
parent3c9399e643a07074d47c9bceca88e8d43ff55d36 (diff)
History Command and Parsing
Diffstat (limited to 'apt-pkg')
-rw-r--r--apt-pkg/history.cc190
-rw-r--r--apt-pkg/history.h80
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 &section)
+{
+ 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 &section);
+// 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