Skip to content

Commit

Permalink
Fix countme bucket calculation
Browse files Browse the repository at this point in the history
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: rpm-software-management#1611
  • Loading branch information
dmnks committed May 8, 2024
1 parent 3aca535 commit 3c885f8
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 1 deletion.
1 change: 1 addition & 0 deletions libdnf/repo/Repo-private.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class Repo::Impl {
void fetch(const std::string & destdir, std::unique_ptr<LrHandle> && h);
std::string getCachedir() const;
std::string getPersistdir() const;
time_t getSystemEpoch() const;
int getAge() const;
void expire();
bool isExpired() const;
Expand Down
34 changes: 33 additions & 1 deletion libdnf/repo/Repo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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 good-enough 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());
Expand Down

0 comments on commit 3c885f8

Please sign in to comment.