From 0121ca7417727a7e1b2703e68077d13bc17e85a4 Mon Sep 17 00:00:00 2001 From: Michal Domonkos Date: Wed, 8 May 2024 12:05:21 +0200 Subject: [PATCH] Fix countme bucket calculation Actually use the system's installation time (if known) as the reference point, instead of the first-ever countme event recorded for the given repo. This is what the dnf.conf(5) man page has been saying all along, the code just never lived up to it. This fixes the following issues: 1. Systems that only reach out to the repos after an initial period of time after their installation appear "younger" than they really are. 2. Prebuilt OS images may include repo persistdirs with countme cookies in them that were created at build time, making all instances spawned from those images (physical machines, VMs or containers) appear much "older" than they really are. 3. System upgrades cause the bucket to be effectively reset to 1 due to the fact that a changed $releasever value causes a new persistdir to be created. Use the machine-id(5) file's mtime as the single source of truth. This file is typically tied to the system's installation or first boot where it's populated by an installer tool or init system, respectively, and is never changed afterwards. Keep the "relative" epoch (first countme event) as a fallback method, though. This is useful on those systems that don't have a machine-id file (such as OCI containers) but are still used long-term. In those cases, system upgrades aren't really a thing so the above point 3 does not apply. Some containers may also choose to bind-mount the machine-id file from the host (such as what toolbox(1) does), in which case their age will be the same as that of the host. Conveniently, that's also what we want, since the purpose of such containers is to blend with the host as much as possible. Fixes: #1611 --- libdnf/repo/Repo-private.hpp | 1 + libdnf/repo/Repo.cpp | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/libdnf/repo/Repo-private.hpp b/libdnf/repo/Repo-private.hpp index 1f659e6fb..88cadf7d6 100644 --- a/libdnf/repo/Repo-private.hpp +++ b/libdnf/repo/Repo-private.hpp @@ -91,6 +91,7 @@ class Repo::Impl { void fetch(const std::string & destdir, std::unique_ptr && h); std::string getCachedir() const; std::string getPersistdir() const; + time_t getSystemEpoch() const; int getAge() const; void expire(); bool isExpired() const; diff --git a/libdnf/repo/Repo.cpp b/libdnf/repo/Repo.cpp index 40b0b68ec..5f2ca29b3 100644 --- a/libdnf/repo/Repo.cpp +++ b/libdnf/repo/Repo.cpp @@ -900,7 +900,7 @@ void Repo::Impl::addCountmeFlag(LrHandle *handle) { // Load the cookie std::string fname = getPersistdir() + "/" + COUNTME_COOKIE; int ver = COUNTME_VERSION; // file format version (for future use) - time_t epoch = 0; // position of first-ever counted window + time_t epoch = 0; // position of first observed window time_t win = COUNTME_OFFSET; // position of last counted window int budget = -1; // budget for this window (-1 = generate) std::ifstream(fname) >> ver >> epoch >> win >> budget; @@ -926,8 +926,15 @@ void Repo::Impl::addCountmeFlag(LrHandle *handle) { // Compute the position of this window win = now - (delta % COUNTME_WINDOW); + + // Compute the epoch from this system's epoch or, if unknown, declare + // this window as the epoch (unless stored in the cookie previously). + time_t sysepoch = getSystemEpoch(); + if (sysepoch) + epoch = sysepoch - ((sysepoch - COUNTME_OFFSET) % COUNTME_WINDOW); if (!epoch) epoch = win; + // Window step (0 at epoch) int step = (win - epoch) / COUNTME_WINDOW; @@ -1221,6 +1228,31 @@ std::string Repo::Impl::getPersistdir() const return result; } +/* Returns this system's installation time ("epoch") as a UNIX timestamp. + * + * Uses the machine-id(5) file's mtime as a single source of truth. This file + * is typically tied to the system's installation or first boot where it's + * populated by an installer tool or init system, respectively, and is never + * changed afterwards. + * + * Some systems, such as containers that don't run an init system, may have the + * file missing, empty or uninitialized, in which case this function returns 0. + */ +time_t Repo::Impl::getSystemEpoch() const +{ + std::string filename = "/etc/machine-id"; + std::string id; + struct stat st; + + if (stat(filename.c_str(), &st) != 0 || !st.st_size) + return 0; + std::ifstream(filename) >> id; + if (id == "uninitialized") + return 0; + + return st.st_mtime; +} + int Repo::Impl::getAge() const { return time(NULL) - mtime(getMetadataPath(MD_TYPE_PRIMARY).c_str());