countme: Add DNF Count Me support

Add support for the DNF Count Me feature [1,2,3] as a standalone
rpm-ostree subcommand called weekly via a systemd timer.

[1] https://fedoraproject.org/wiki/Changes/DNF_Better_Counting
[2] https://dnf.readthedocs.io/en/latest/conf_ref.html?highlight=countme#options-for-both-main-and-repo
[3] https://github.com/rpm-software-management/ci-dnf-stack/blob/master/dnf-behave-tests/features/countme.feature
This commit is contained in:
Timothée Ravier 2020-10-13 17:46:00 +02:00 committed by OpenShift Merge Robot
parent 0e9941495e
commit 6cfdb1f585
15 changed files with 725 additions and 4 deletions

View File

@ -7,7 +7,7 @@ edition = "2018"
[dependencies]
anyhow = "1.0.35"
paste = "1.0"
serde = "1.0.118"
serde = { version = "1.0.118", features = ["derive"] }
serde_derive = "1.0.118"
serde_json = "1.0.60"
serde_yaml = "0.8.14"
@ -38,6 +38,8 @@ subprocess = "0.2.6"
chrono = { version = "0.4.19", features = ["serde"] }
libdnf-sys = { path = "rust/libdnf-sys", version = "0.1.0" }
memfd = "0.3.0"
rust-ini = "0.16.0"
os-release = "0.1.0"
[build-dependencies]
cbindgen = "0.16.0"

View File

@ -17,7 +17,11 @@
man1_MANS = rpm-ostree.1
man5_MANS = rpm-ostreed.conf.5
man8_MANS = rpm-ostreed-automatic.service.8 rpm-ostreed-automatic.timer.8
man8_MANS = rpm-ostreed-automatic.service.8 \
rpm-ostreed-automatic.timer.8 \
rpm-ostree-countme.service.8 \
rpm-ostree-countme.timer.8
XSLTPROC_FLAGS = \
--nonet \
@ -39,4 +43,7 @@ rpm-ostreed.conf.5: man/rpm-ostreed.conf.xml Makefile
rpm-ostreed-automatic.service.8 rpm-ostreed-automatic.timer.8: man/rpm-ostreed-automatic.xml Makefile
$(AM_V_GEN) $(XSLTPROC) $(XSLTPROC_FLAGS_MAN) $<
rpm-ostree-countme.service.8 rpm-ostree-countme.timer.8: man/rpm-ostree-countme.xml Makefile
$(AM_V_GEN) $(XSLTPROC) $(XSLTPROC_FLAGS_MAN) $<
CLEANFILES += $(man1_MANS) $(man5_MANS) $(man8_MANS)

View File

@ -32,8 +32,9 @@ librpmostreeinternals_la_SOURCES = \
src/app/rpmostree-builtin-reload.cxx \
src/app/rpmostree-builtin-rebase.cxx \
src/app/rpmostree-builtin-cancel.cxx \
src/app/rpmostree-builtin-cliwrap.cxx \
src/app/rpmostree-builtin-cleanup.cxx \
src/app/rpmostree-builtin-cliwrap.cxx \
src/app/rpmostree-builtin-countme.cxx \
src/app/rpmostree-builtin-initramfs.cxx \
src/app/rpmostree-builtin-initramfs-etc.cxx \
src/app/rpmostree-builtin-livefs.cxx \

41
docs/countme.md Normal file
View File

@ -0,0 +1,41 @@
---
nav_order: 4
---
# DNF Count Me support
{: .no_toc }
1. TOC
{:toc}
Classic DNF based operating systems can use the [DNF Count Me feature][countme]
to anonymously report how long a system has been running without impacting the
user privacy. This is implemented as an additional `countme` variable added to
requests made to fetch RPM repository metadata. On those systems, this value is
added randomly to requests made automatically via the `dnf-makecache.timer` or
via explicit calls to `dnf update` or `dnf install`.
However, this does not work for `rpm-ostree` based systems as in the default
case (no package overlayed on top of the base commit), `rpm-ostree` will not
fetch any RPM repository metadata at all.
Thus `rpm-ostree` includes a distinct timer (`rpm-ostree-countme.timer`),
triggered weekly, that implement the DNF Count Me functionality in a
standalone way.
## Disabling DNF Count Me on a system
To disable this feature, you need to update the Count Me option in all RPM
repository configuration files:
```
sed -i 's/countme=1/countme=0/g' /etc/yum.repos.d/*.repo
```
You should also stop and disable the `rpm-ostree-countme.timer`:
```
$ systemctl disable --now rpm-ostree-countme.timer
```
[countme]: https://fedoraproject.org/wiki/Changes/DNF_Better_Counting

View File

@ -0,0 +1,80 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
Copyright 2020 Colin Walters <walters@verbum.org>
Copyright 2020 Timothée Ravier <travier@redhat.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
-->
<refentry id="rpm-ostree-countme.service">
<refentryinfo>
<title>rpm-ostree-countme</title>
<productname>rpm-ostree</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Timothée</firstname>
<surname>Ravier</surname>
<email>travier@redhat.com</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>rpm-ostree-countme</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>rpm-ostree-countme.service</refname>
<refname>rpm-ostree-countme.timer</refname>
<refpurpose>DNF Count Me support in rpm-ostree</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>rpm-ostree-countme.service</filename></para>
<para><filename>rpm-ostree-countme.timer</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
The service unit enacts the Count Me reporting implemented as a hidden command in rpm-ostree.
</para>
<para>
The timer unit triggers Count Me reporting weekly at a random time.
Disabling or masking this unit effectively disables Count Me reporting, regardless of the setting in RPM repository configuration files.
See <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more information on how to control systemd timers.
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>rpm-ostree</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>dnf</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

124
rust/src/countme.rs Normal file
View File

@ -0,0 +1,124 @@
/*
* Copyright (C) 2020 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
use anyhow::{bail, Context, Result};
use curl::easy::Easy;
use os_release::OsRelease;
use std::path;
mod cookie;
mod repo;
/// Default variant name used in User Agent
const DEFAULT_VARIANT_ID: &str = "unknown";
/// Send a request to 'url' with 'ua' as User Agent.
/// This sends a GET request and discards the body as this is what is currently
/// expected on the Fedora infrastructure side.
/// Once this is fixed, we can switch to a HEAD request to reduce the footprint:
/// let mut handle = Easy::new().nobody(true)?;
fn send_countme(url: &str, ua: &str) -> Result<()> {
println!("Sending request to: {}", url);
let mut handle = Easy::new();
handle.follow_location(true)?;
handle.fail_on_error(true)?;
handle.url(&url)?;
handle.useragent(&ua)?;
{
let mut transfer = handle.transfer();
transfer.write_function(|new_data| Ok(new_data.len()))?;
transfer.perform()?;
}
Ok(())
}
/// Main entrypoint for countme
pub(crate) fn countme_entrypoint(_args: Vec<String>) -> Result<()> {
// Silently skip if we are not run on an ostree booted system
if !path::Path::new("/run/ostree-booted").exists() {
bail!("Not running on an ostree based system");
}
// Load repo configs and keep only those enabled, with a metalink and countme=1
let repos: Vec<_> = self::repo::all()?
.into_iter()
.filter(|r| r.count_me())
.collect();
if repos.is_empty() {
println!("No enabled repositories with countme=1");
return Ok(());
}
// Load timestamp cookie
let cookie = cookie::Cookie::new().context("Could not read existing cookie")?;
// Skip this run if we are not in a new counting window
if cookie.existing_window() {
println!("Skipping: Not in a new counting window");
return Ok(());
}
// Read /etc/os-release
let release: OsRelease = OsRelease::new()?;
let variant: &str = release
.extra
.get("VARIANT_ID")
.map_or(DEFAULT_VARIANT_ID, |s| s);
// Setup User Agent. The format is:
// libdnf (NAME VERSION_ID; VARIANT_ID; OS.BASEARCH)
// libdnf (Fedora 31; server; Linux.x86_64)
// See `user_agent` option in:
// https://dnf.readthedocs.io/en/latest/conf_ref.html?highlight=user_agent#options-for-both-main-and-repo
let ua = format!(
"rpm-ostree ({} {}; {}; {}.{})",
release.name,
release.version_id,
variant,
"Linux",
std::env::consts::ARCH
);
println!("Using User Agent: {}", ua);
// Compute the value to send as window counter
let counter = cookie.get_window_counter();
// Send Get requests, track successfully ones and do not exit on failures
let successful = repos.iter().fold(0, |acc, r| {
let url = format!("{}&countme={}", &r.metalink(&release.version_id), counter);
match send_countme(&url, &ua) {
Ok(_) => acc + 1,
Err(e) => {
eprintln!("Request '{}' failed: {}", url, e);
acc
}
}
});
// Update cookie timestamp only if at least one request is successful
if successful == 0 {
bail!("No request successful");
}
println!("Successful requests: {}/{}", successful, repos.len());
if let Err(e) = cookie.persist() {
// Do not exit with a non zero code here as we have still made at least
// one successful request thus we have been counted.
eprintln!("Failed to persist cookie: {}", e);
}
Ok(())
}

250
rust/src/countme/cookie.rs Normal file
View File

@ -0,0 +1,250 @@
/*
* Copyright (C) 2020 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
use anyhow::{bail, Result};
use chrono::prelude::*;
use openat_ext::OpenatDirExt;
use serde::{Deserialize, Serialize};
use std::io::Read;
/// State directory used to store the countme cookie
const STATE_DIR: &str = "/var/lib/rpm-ostree-countme";
/// Cookie file name
const COUNTME_COOKIE: &str = "countme";
/// Width of the sliding time window (in seconds): 1 week
const COUNTME_WINDOW: i64 = 7 * 24 * 60 * 60;
/// Starting point of the sliding time window relative to the UNIX epoch:
/// Monday (1970-01-05 00:00:00 UTC)
/// Allows for aligning the window with a specific weekday
const COUNTME_OFFSET: i64 = 345600;
/// Cookie v0 JSON file format
///
/// The values stored in this cookie are the same as the ones used in the DNF
/// Count Me Cookie with the exception of the COUNTME_BUDGET which is not
/// included as this implementation directly queries all repo metalinks once:
/// - epoch: the timestamp of the first ever counted window
/// - window: the timestamp of the last successfully counted window
///
/// See the libdnf implementation at:
/// https://github.com/rpm-software-management/libdnf/blob/95b88b141a3f97feb94eadb2480f6857b6d1fcae/libdnf/repo/Repo.cpp#L1038
///
/// This cookie is marked as version 0 similarly to how libdnf defines a version
/// for their cookie to simplify potential future updates but no additional code
/// has been added yet to handle multiple versions as we do not need it yet.
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct CookieV0 {
epoch: i64,
window: i64,
}
impl CookieV0 {
/// Try to parse a string as a v0 cookie
fn new(string: &str) -> Result<Self> {
let c: CookieV0 = serde_json::from_str(string)?;
if c.epoch <= 0 {
bail!("Invalid value for epoch: '{}'", c.epoch);
}
if c.window <= 0 {
bail!("Invalid value for window: '{}'", c.window);
}
Ok(c)
}
/// Export a v0 cookie to JSON for persistent storage
fn to_json(epoch: i64, window: i64) -> Result<String> {
Ok(serde_json::to_string(&CookieV0 { epoch, window })?)
}
}
/// Internal representation of the values loaded from the versioned cookie
/// format.
#[derive(Clone, Debug)]
pub struct Cookie {
epoch: i64,
window: i64,
now: i64,
}
impl Cookie {
/// Load cookie timestamps from persistent directory if it exists.
/// Returns an error if we can not read an existing cookie
/// Returns a default cookie (counting never started) in all other cases.
pub fn new() -> Result<Self> {
// Start default window at COUNTME_OFFSET to avoid negative values
let now = Utc::now().timestamp();
let mut c = Cookie {
epoch: now,
window: COUNTME_OFFSET,
now,
};
// Read cookie values from the state persisted on the filesystem
let mut content = String::new();
match openat::Dir::open(STATE_DIR)?.open_file_optional(COUNTME_COOKIE)? {
Some(mut f) => f.read_to_string(&mut content)?,
None => return Ok(c),
};
match CookieV0::new(&content) {
Err(e) => eprintln!("Ignoring existing cookie: {}", e),
Ok(cookie_v0) => {
c.epoch = cookie_v0.epoch;
c.window = cookie_v0.window;
}
};
// Reset epoch if it is in the future
if c.epoch > c.now {
c.epoch = c.now;
}
Ok(c)
}
/// Compute the Count Me window for the epoch stored in the cookie
fn epoch_window(&self) -> i64 {
(self.epoch - COUNTME_OFFSET) / COUNTME_WINDOW
}
/// Compute the Count Me window for the previous window stored in the cookie
fn previous_window(&self) -> i64 {
(self.window - COUNTME_OFFSET) / COUNTME_WINDOW
}
/// Compute the Count Me window for current time
fn current_window(&self) -> i64 {
(self.now - COUNTME_OFFSET) / COUNTME_WINDOW
}
/// Are we still in the same window as the one we loaded from the cookie?
pub fn existing_window(&self) -> bool {
self.current_window() <= self.previous_window()
}
// Count Me window logic
// https://dnf.readthedocs.io/en/latest/conf_ref.html?highlight=countme#options-for-both-main-and-repo
// https://github.com/rpm-software-management/libdnf/blob/95b88b141a3f97feb94eadb2480f6857b6d1fcae/libdnf/repo/Repo.cpp#L1038
pub fn get_window_counter(&self) -> i64 {
// Start counting windows from 1
let mut counter = self.current_window() - self.epoch_window() + 1;
if counter <= 0 {
// Should never happen as this is checked at creation time
counter = 1;
}
match counter {
1 => 1,
2..=4 => 2,
5..=24 => 3,
_ => 4, // >= 25
}
}
/// Update cookie timestamps that are persisted on disk
pub fn persist(&self) -> Result<()> {
openat::Dir::open(STATE_DIR)?.write_file_contents(
COUNTME_COOKIE,
0o644,
CookieV0::to_json(self.epoch, self.now)?,
)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_cookie_v0() {
let line = r#"{ "epoch": 1607706068, "window": 1607706068 }"#;
assert_eq!(
CookieV0::new(line).unwrap(),
CookieV0 {
epoch: 1607706068,
window: 1607706068,
}
);
let line = r#"{ "epoch": 1600006068, "window": 1607706068 }"#;
assert_eq!(
CookieV0::new(line).unwrap(),
CookieV0 {
epoch: 1600006068,
window: 1607706068,
}
);
let line = r#"{ "epoch": -1, "window": 1607706068 }"#;
assert!(CookieV0::new(line).is_err());
let line = r#"{ "epoch": "1600006068", window: -1 }"#;
assert!(CookieV0::new(line).is_err());
}
#[test]
fn test_window_counter() {
// Invalid cookie (epoch is in the future)
let cookie = Cookie {
epoch: 1607703629, // Friday, December 11, 2020 4:20:29 PM
window: COUNTME_OFFSET, // Monday, January 5, 1970 12:00:00 AM
now: 976724429, // Wednesday, December 13, 2000 4:20:29 PM
};
assert_eq!(cookie.get_window_counter(), 1);
// Fresh cookie (epoch = now)
let cookie = Cookie {
epoch: 1607703629, // Friday, December 11, 2020 4:20:29 PM
window: COUNTME_OFFSET, // Monday, January 5, 1970 12:00:00 AM
now: 1607703629, // Friday, December 11, 2020 4:20:29 PM
};
assert_eq!(cookie.get_window_counter(), 1);
// First week
let cookie = Cookie {
epoch: 1607703629, // Friday, December 11, 2020 4:20:29 PM
window: COUNTME_OFFSET, // Monday, January 5, 1970 12:00:00 AM
now: 1607876429, // Sunday, December 13, 2020 4:20:29 PM
};
assert_eq!(cookie.get_window_counter(), 1);
// First month
let cookie = Cookie {
epoch: 1607703629, // Friday, December 11, 2020 4:20:29 PM
window: COUNTME_OFFSET, // Monday, January 5, 1970 12:00:00 AM
now: 1608135629, // Wednesday, December 16, 2020 4:20:29 PM
};
assert_eq!(cookie.get_window_counter(), 2);
// First six months
let cookie = Cookie {
epoch: 1607703629, // Friday, December 11, 2020 4:20:29 PM
window: COUNTME_OFFSET, // Monday, January 5, 1970 12:00:00 AM
now: 1613838029, // Saturday, February 20, 2021 4:20:29 PM
};
assert_eq!(cookie.get_window_counter(), 3);
// More than six months
let cookie = Cookie {
epoch: 1607703629, // Friday, December 11, 2020 4:20:29 PM
window: COUNTME_OFFSET, // Monday, January 5, 1970 12:00:00 AM
now: 1647793229, // Sunday, March 20, 2022 4:20:29 PM
};
assert_eq!(cookie.get_window_counter(), 4);
}
}

119
rust/src/countme/repo.rs Normal file
View File

@ -0,0 +1,119 @@
/*
* Copyright (C) 2020 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
use anyhow::{Context, Result};
use std::fs;
use std::path::PathBuf;
use ini::Ini;
use crate::utils;
/// Location for DNF repositories configuration
pub const YUM_REPOS_D: &str = "/etc/yum.repos.d";
/// Repository configuration
/// Not exhaustive and only includes the options needed for Count Me support.
#[derive(Debug)]
pub struct Repo {
name: String,
enabled: bool,
count_me: bool,
meta_link: String,
}
/// From https://github.com/rpm-software-management/libdnf/blob/45981d5f53980dac362900df65bcb2652aa8d7c7/libdnf/conf/OptionBool.hpp#L30-L31
fn is_true(string: &str) -> bool {
string == "1" || string == "yes" || string == "true" || string == "on"
}
/// Read all repository configuration files from the default location
pub fn all() -> Result<Vec<Repo>> {
let configs = fs::read_dir(YUM_REPOS_D)
.with_context(|| format!("Could not list files in: {}", YUM_REPOS_D))?;
let mut repos = Vec::new();
for c in configs {
let path = c?.path();
match parse_repo_file(&path) {
Err(e) => {
eprintln!(
"Failed to parse repo config file '{}': {}",
path.display(),
e
)
}
Ok(mut r) => repos.append(&mut r),
}
}
Ok(repos)
}
/// Read repository configuration from a file
fn parse_repo_file(path: &PathBuf) -> Result<Vec<Repo>> {
let i = Ini::load_from_file(path).with_context(|| "Could not parse as INI".to_string())?;
let mut repos = Vec::new();
for (sec, prop) in i.iter() {
let mut repo = match sec {
None => {
continue;
}
Some(s) => Repo {
name: String::from(s),
enabled: false,
count_me: false,
meta_link: "".to_string(),
},
};
for (k, v) in prop.iter() {
match k {
"countme" => {
if is_true(v) {
repo.count_me = true
}
}
"enabled" => {
if is_true(v) {
repo.enabled = true
}
}
"metalink" => repo.meta_link = String::from(v),
_ => {}
}
}
repos.push(repo);
}
Ok(repos)
}
impl Repo {
/// Returns true if this repo is
/// - enabled
/// - configured for sending a Count Me request
/// - has a non-empty metalink URL
pub fn count_me(&self) -> bool {
self.enabled && self.count_me && !self.meta_link.is_empty()
}
/// Get the metalink URL for the repo with variables replaced
pub fn metalink(&self, version_id: &str) -> String {
self.meta_link
.clone()
.replace("$releasever", &version_id)
.replace("$basearch", &utils::get_rpm_basearch())
}
}

View File

@ -129,12 +129,19 @@ mod ffi {
fn add_group_content(self: &mut PasswdDB, rootfs: i32, group_path: &str) -> Result<()>;
fn add_passwd_content(self: &mut PasswdDB, rootfs: i32, passwd_path: &str) -> Result<()>;
}
// countme.rs
extern "Rust" {
fn countme_entrypoint(argv: Vec<String>) -> Result<()>;
}
}
mod client;
pub(crate) use client::*;
mod cliwrap;
pub use cliwrap::*;
mod countme;
pub use countme::*;
mod composepost;
pub(crate) use composepost::*;
mod core;

View File

@ -321,3 +321,17 @@ pub(crate) fn sealed_memfd(description: &str, content: &[u8]) -> Result<i32> {
mfd.add_seals(&seals)?;
Ok(mfd.into_file().into_raw_fd())
}
/// Map Rust architecture constants to the ones used by DNF.
/// Rust architecture constants: https://doc.rust-lang.org/std/env/consts/constant.ARCH.html
/// DNF mapping: https://github.com/rpm-software-management/dnf/blob/4.5.2/dnf/rpm/__init__.py#L88
pub(crate) fn get_rpm_basearch() -> String {
match std::env::consts::ARCH {
"arm" => "armhfp".to_string(),
"powerpc" => "ppc".to_string(),
"powerpc64" => "ppc64".to_string(),
"sparc64" => "sparc".to_string(),
"x86" => "i386".to_string(),
s => s.to_string(),
}
}

View File

@ -132,6 +132,8 @@ static RpmOstreeCommand commands[] = {
NULL, rpmostree_builtin_finalize_deployment },
{ "cliwrap", static_cast<RpmOstreeBuiltinFlags>(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | RPM_OSTREE_BUILTIN_FLAG_HIDDEN),
NULL, rpmostree_builtin_cliwrap },
{ "countme", static_cast<RpmOstreeBuiltinFlags>(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | RPM_OSTREE_BUILTIN_FLAG_HIDDEN),
NULL, rpmostree_builtin_countme },
{ NULL }
};

View File

@ -0,0 +1,12 @@
[Unit]
Description=Weekly rpm-ostree Count Me reporting
Documentation=man:rpm-ostree-countme.service(8)
ConditionPathExists=/run/ostree-booted
[Service]
Type=oneshot
User=rpm-ostree
DynamicUser=yes
StateDirectory=rpm-ostree-countme
StateDirectoryMode=750
ExecStart=/usr/bin/rpm-ostree countme

View File

@ -0,0 +1,13 @@
[Unit]
Description=Weekly rpm-ostree Count Me timer
Documentation=man:rpm-ostree-countme.timer(8)
ConditionPathExists=/run/ostree-booted
[Timer]
# Trigger weekly with a random delay
OnCalendar=weekly UTC
AccuracySec=1u
RandomizedDelaySec=1w
[Install]
WantedBy=timers.target

View File

@ -0,0 +1,48 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2020 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <string.h>
#include <glib-unix.h>
#include <gio/gio.h>
#include "rpmostree-builtins.h"
#include "rpmostree-libbuiltin.h"
#include "rpmostree-cxxrs.h"
#include <libglnx.h>
gboolean
rpmostree_builtin_countme (int argc,
char **argv,
RpmOstreeCommandInvocation *invocation,
GCancellable *cancellable,
GError **error)
{
if (argc > 1)
return glnx_throw (error, "countme: No argument supported");
rust::Vec<rust::String> rustargv;
for (int i = 1; i < argc; i++)
rustargv.push_back(std::string(argv[i]));
rpmostreecxx::countme_entrypoint (rustargv);
return TRUE;
}

View File

@ -30,8 +30,9 @@ G_BEGIN_DECLS
RpmOstreeCommandInvocation *invocation, \
GCancellable *cancellable, GError **error)
BUILTINPROTO(compose);
BUILTINPROTO(cliwrap);
BUILTINPROTO(compose);
BUILTINPROTO(countme);
BUILTINPROTO(upgrade);
BUILTINPROTO(reload);
BUILTINPROTO(usroverlay);