Initial support for automatic updates

This patch introduces a new `AutomaticUpdatePolicy` configuration. This
was a long time coming for rpm-ostree, given that its update model makes
it extremely apt for such a feature.

The config supports a `check` mode, which should be very useful to
Atomic Workstation users, as well as a `reboot` mode, which could be
used in its present form in simple single node Atomic Host situations.

There is still a lot of work to be done, including integrating
advisories, and supporting a `deploy` mode. This feature hopefully will
be leveraged as well by higher-level projects like GNOME Software and
Cockpit.

Closes: #1147
Approved by: cgwalters
This commit is contained in:
Jonathan Lebon 2017-12-15 17:01:46 +00:00 committed by Atomic Bot
parent 3e9c6cf230
commit 51fb641305
34 changed files with 1763 additions and 90 deletions

View File

@ -74,15 +74,26 @@ librpmostreed_la_LIBADD = \
dbusconf_DATA = $(srcdir)/src/daemon/org.projectatomic.rpmostree1.conf
dbusconfdir = ${sysconfdir}/dbus-1/system.d
systemdunit_in_files = $(srcdir)/src/daemon/rpm-ostreed.service.in
systemdunit_DATA = $(systemdunit_in_files:.service.in=.service)
systemdunit_service_in_files = \
$(srcdir)/src/daemon/rpm-ostreed.service.in \
$(srcdir)/src/daemon/rpm-ostreed-automatic.service.in \
$(NULL)
systemdunit_service_files = $(systemdunit_service_in_files:.service.in=.service)
systemdunit_timer_files = $(srcdir)/src/daemon/rpm-ostreed-automatic.timer
systemdunit_DATA = \
$(systemdunit_service_files) \
$(systemdunit_timer_files) \
$(NULL)
systemdunitdir = $(prefix)/lib/systemd/system/
if BUILDOPT_ASAN
daemon_asan_options = -e s,@SYSTEMD_ENVIRON\@,Environment=ASAN_OPTIONS=detect_leaks=false,
else
daemon_asan_options = -e /@SYSTEMD_ENVIRON\@/d
endif
$(systemdunit_DATA): Makefile
$(systemdunit_service_files): Makefile
$(SED_SUBST) $(daemon_asan_options) $@.in > $@
# We keep this stub script around to have SELinux labeling work,
@ -119,10 +130,11 @@ EXTRA_DIST += \
$(polkit_policy_DATA) \
$(sysconf_DATA) \
$(service_in_files) \
$(systemdunit_in_files) \
$(systemdunit_service_in_files) \
$(systemdunit_timer_files) \
$(NULL)
CLEANFILES += \
$(service_DATA) \
$(systemdunit_DATA) \
$(systemdunit_service_files) \
$(NULL)

View File

@ -62,6 +62,8 @@ librpmostreepriv_la_SOURCES = \
src/libpriv/rpmostree-editor.h \
src/libpriv/libsd-locale-util.c \
src/libpriv/libsd-locale-util.h \
src/libpriv/libsd-time-util.c \
src/libpriv/libsd-time-util.h \
src/libpriv/rpmostree-libarchive-input-stream.c \
src/libpriv/rpmostree-libarchive-input-stream.h \
$(NULL)

View File

@ -18,7 +18,7 @@ if BUILDOPT_ASAN
AM_TESTS_ENVIRONMENT += BUILDOPT_ASAN=yes ASAN_OPTIONS=detect_leaks=false
endif
testbin_cppflags = $(AM_CPPFLAGS) -I $(srcdir)/src/libpriv -I $(srcdir)/libglnx -I $(srcdir)/tests/common
testbin_cppflags = $(AM_CPPFLAGS) -I $(srcdir)/src/lib -I $(srcdir)/src/libpriv -I $(srcdir)/libglnx -I $(srcdir)/tests/common
testbin_cflags = $(AM_CFLAGS) $(PKGDEP_RPMOSTREE_CFLAGS)
testbin_ldadd = $(PKGDEP_RPMOSTREE_LIBS) librpmostree-1.la librpmostreepriv.la
@ -56,10 +56,18 @@ uninstalled_test_scripts = \
tests/check/test-ucontainer.sh \
$(NULL)
uninstalled_test_extra_programs = dbus-run-session
uninstalled_test_extra_programs = \
inject-pkglist \
dbus-run-session \
$(NULL)
dbus_run_session_SOURCES = tests/utils/dbus-run-session.c
inject_pkglist_CPPFLAGS = $(testbin_cppflags)
inject_pkglist_CFLAGS = $(testbin_cflags)
inject_pkglist_LDADD = $(testbin_ldadd) libtest.la
inject_pkglist_SOURCES = tests/utils/inject-pkglist.c
check-local:
@echo " *** NOTE ***"
@echo " *** NOTE ***"
@ -78,7 +86,7 @@ vmsync:
fi; \
env $(BASE_TESTS_ENVIRONMENT) ./tests/vmcheck/sync.sh
vmoverlay:
vmoverlay: inject-pkglist
@set -e; if [ -z "$(SKIP_VMOVERLAY)" ]; then \
if [ -z "$(SKIP_INSTALL)" ]; then \
env $(BASE_TESTS_ENVIRONMENT) ./tests/vmcheck/install.sh; \

View File

@ -67,12 +67,27 @@ Boston, MA 02111-1307, USA.
</para>
<variablelist>
<varlistentry>
<term><varname>AutomaticUpdatePolicy=</varname></term>
<listitem>
<para>Controls the automatic update policy. Currently "none" or "check".
"none" disables automatic updates. "check" downloads just enough metadata to check
for updates and display them in <command>rpm-ostree status</command>. Defaults to
"none".</para>
<para>Automatic updates enablement and frequency are controlled by the
<command>rpm-ostreed-automatic.timer</command> unit. <!-- XXX: needs man page -->
See <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more information on how to control systemd timers.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>IdleExitTimeout=</varname></term>
<listitem>
<para>Controls the time in seconds of inactivity before the daemon exits. Use 0 to
disable auto-exit.</para>
disable auto-exit. Defaults to 60.</para>
</listitem>
</varlistentry>
<!--

View File

@ -26,6 +26,7 @@
#include <glib-unix.h>
#include <gio/gunixoutputstream.h>
#include <json-glib/json-glib.h>
#include <libdnf/libdnf.h>
#include "rpmostree-builtins.h"
#include "rpmostree-libbuiltin.h"
@ -34,9 +35,13 @@
#include "rpmostree-core.h"
#include "rpmostree-rpm-util.h"
#include "libsd-locale-util.h"
#include "libsd-time-util.h"
#include <libglnx.h>
#define RPMOSTREE_AUTOMATIC_SERVICE_OBJPATH \
"/org/freedesktop/systemd1/unit/rpm_2dostreed_2dautomatic_2eservice"
static gboolean opt_pretty;
static gboolean opt_verbose;
static gboolean opt_json;
@ -167,8 +172,70 @@ gv_nevra_to_evr (GString *buffer,
PKG_NEVRA_FLAGS_EPOCH_VERSION_RELEASE);
}
static gboolean
get_last_auto_update_run (GDBusConnection *connection,
char **out_last_run,
gboolean *out_fail,
GCancellable *cancellable,
GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Querying systemd for last auto-update run", error);
g_autoptr(GDBusProxy) unit_proxy =
g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL,
"org.freedesktop.systemd1", RPMOSTREE_AUTOMATIC_SERVICE_OBJPATH,
"org.freedesktop.systemd1.Unit", cancellable, error);
if (!unit_proxy)
return FALSE;
g_autoptr(GVariant) state_val =
g_dbus_proxy_get_cached_property (unit_proxy, "ActiveState");
/* let's not error out if we can't msg systemd (e.g. bad sepol); just mark as unknown */
if (state_val == NULL)
{
*out_fail = FALSE;
*out_last_run = g_strdup ("unknown");
return TRUE; /* NB early return */
}
const char *state = g_variant_get_string (state_val, NULL);
if (g_str_equal (state, "failed"))
{
*out_fail = TRUE;
return TRUE; /* NB early return */
}
g_autoptr(GDBusProxy) service_proxy =
g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL,
"org.freedesktop.systemd1", RPMOSTREE_AUTOMATIC_SERVICE_OBJPATH,
"org.freedesktop.systemd1.Service", cancellable, error);
if (!service_proxy)
return FALSE;
g_autoptr(GVariant) t_val =
g_dbus_proxy_get_cached_property (service_proxy, "ExecMainExitTimestamp");
g_autofree char *last_run = NULL;
if (t_val)
{
guint64 t = g_variant_get_uint64 (t_val);
if (t > 0)
{
char time_rel[FORMAT_TIMESTAMP_RELATIVE_MAX] = "";
libsd_format_timestamp_relative (time_rel, sizeof(time_rel), t);
last_run = g_strdup (time_rel);
}
}
*out_fail = FALSE;
*out_last_run = g_steal_pointer (&last_run);
return TRUE;
}
static gboolean
print_daemon_state (RPMOSTreeSysroot *sysroot_proxy,
GBusType bus_type,
GCancellable *cancellable,
GError **error)
{
@ -177,7 +244,38 @@ print_daemon_state (RPMOSTreeSysroot *sysroot_proxy,
cancellable, error))
return FALSE;
const char *policy = rpmostree_sysroot_get_automatic_update_policy (sysroot_proxy);
g_print ("State: %s", txn_proxy ? "busy" : "idle");
if (g_str_equal (policy, "none"))
g_print ("; auto updates disabled\n");
else
{
g_print ("; auto updates enabled ");
/* don't try to get info from systemd if we're not on the system bus */
if (bus_type != G_BUS_TYPE_SYSTEM)
g_print ("(%s)\n", policy);
else
{
gboolean failed;
g_autofree char *last_run = NULL;
GDBusConnection *connection =
g_dbus_proxy_get_connection (G_DBUS_PROXY (sysroot_proxy));
if (!get_last_auto_update_run (connection, &last_run, &failed, cancellable, error))
return FALSE;
if (failed)
g_print ("(%s; %s%slast run failed%s%s)\n", policy,
get_red_start (), get_bold_start (), get_bold_end (), get_red_end ());
else if (last_run)
/* e.g. "last check 4h 32min ago" */
g_print ("(%s; last run %s)\n", policy, last_run);
else
g_print ("(%s; no runs since boot)\n", policy);
}
}
if (txn_proxy)
{
@ -575,6 +673,7 @@ rpmostree_builtin_status (int argc,
glnx_unref_object RPMOSTreeSysroot *sysroot_proxy = NULL;
_cleanup_peer_ GPid peer_pid = 0;
GBusType bus_type;
if (!rpmostree_option_context_parse (context,
option_entries,
&argc, &argv,
@ -582,7 +681,7 @@ rpmostree_builtin_status (int argc,
cancellable,
NULL, NULL,
&sysroot_proxy,
&peer_pid, NULL,
&peer_pid, &bus_type,
error))
return FALSE;
@ -597,6 +696,9 @@ rpmostree_builtin_status (int argc,
return FALSE;
g_autoptr(GVariant) deployments = rpmostree_sysroot_dup_deployments (sysroot_proxy);
g_autoptr(GVariant) cached_update = NULL;
if (rpmostree_os_get_has_cached_update_rpm_diff (os_proxy))
cached_update = rpmostree_os_dup_cached_update (os_proxy);
if (opt_json || opt_jsonpath)
{
@ -610,6 +712,13 @@ rpmostree_builtin_status (int argc,
JsonNode *txn_node =
txn ? json_gvariant_serialize (txn) : json_node_new (JSON_NODE_NULL);
json_builder_add_value (builder, txn_node);
json_builder_set_member_name (builder, "cached-update");
JsonNode *cached_update_node;
if (cached_update)
cached_update_node = json_gvariant_serialize (cached_update);
else
cached_update_node = json_node_new (JSON_NODE_NULL);
json_builder_add_value (builder, cached_update_node);
json_builder_end_object (builder);
JsonNode *json_root = json_builder_get_root (builder);
@ -638,11 +747,21 @@ rpmostree_builtin_status (int argc,
}
else
{
if (!print_daemon_state (sysroot_proxy, cancellable, error))
if (!print_daemon_state (sysroot_proxy, bus_type, cancellable, error))
return FALSE;
if (!print_deployments (sysroot_proxy, deployments, cancellable, error))
return FALSE;
const char *policy = rpmostree_sysroot_get_automatic_update_policy (sysroot_proxy);
gboolean auto_updates_enabled = (!g_str_equal (policy, "none"));
if (cached_update && auto_updates_enabled)
{
g_print ("\n");
if (!rpmostree_print_cached_update (cached_update, opt_verbose,
cancellable, error))
return FALSE;
}
}
return TRUE;

View File

@ -39,6 +39,7 @@ static gboolean opt_check;
static gboolean opt_upgrade_unchanged_exit_77;
static gboolean opt_cache_only;
static gboolean opt_download_only;
static char *opt_automatic;
/* "check-diff" is deprecated, replaced by "preview" */
static GOptionEntry option_entries[] = {
@ -51,6 +52,7 @@ static GOptionEntry option_entries[] = {
{ "cache-only", 'C', 0, G_OPTION_ARG_NONE, &opt_cache_only, "Do not download latest ostree and RPM data", NULL },
{ "download-only", 0, 0, G_OPTION_ARG_NONE, &opt_download_only, "Just download latest ostree and RPM data, don't deploy", NULL },
{ "upgrade-unchanged-exit-77", 0, 0, G_OPTION_ARG_NONE, &opt_upgrade_unchanged_exit_77, "If no upgrade is available, exit 77", NULL },
{ "trigger-automatic-update-policy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_automatic, "For automated use only; triggered by automatic timer", NULL },
{ NULL }
};
@ -112,13 +114,29 @@ rpmostree_builtin_upgrade (int argc,
g_autoptr(GVariant) previous_deployment = rpmostree_os_dup_default_deployment (os_proxy);
if (opt_preview || opt_check)
const gboolean check_or_preview = (opt_check || opt_preview);
if (opt_automatic || check_or_preview)
{
if (!rpmostree_os_call_download_update_rpm_diff_sync (os_proxy,
GVariantDict dict;
g_variant_dict_init (&dict, NULL);
g_variant_dict_insert (&dict, "mode", "s", check_or_preview ? "check" : "auto");
g_autoptr(GVariant) options = g_variant_ref_sink (g_variant_dict_end (&dict));
gboolean auto_updates_enabled;
if (!rpmostree_os_call_automatic_update_trigger_sync (os_proxy,
options,
&auto_updates_enabled,
&transaction_address,
cancellable,
error))
return FALSE;
if (!auto_updates_enabled)
{
/* print something for the benefit of the journal */
g_print ("Automatic updates are not enabled; exiting...\n");
return TRUE; /* Note early return */
}
}
else
{
@ -168,27 +186,24 @@ rpmostree_builtin_upgrade (int argc,
error))
return FALSE;
if (opt_preview || opt_check)
if (check_or_preview)
{
g_autoptr(GVariant) result = NULL;
g_autoptr(GVariant) details = NULL;
g_autoptr(GVariant) cached_update = NULL;
if (rpmostree_os_get_has_cached_update_rpm_diff (os_proxy))
cached_update = rpmostree_os_dup_cached_update (os_proxy);
if (!rpmostree_os_call_get_cached_update_rpm_diff_sync (os_proxy,
"",
&result,
&details,
cancellable,
error))
return FALSE;
if (g_variant_n_children (result) == 0)
if (!cached_update)
{
g_print ("No updates available.\n");
invocation->exit_code = RPM_OSTREE_EXIT_UNCHANGED;
return TRUE;
}
if (!opt_check)
rpmostree_print_package_diffs (result);
else
{
/* preview --> verbose (i.e. we want the diff) */
if (!rpmostree_print_cached_update (cached_update, opt_preview,
cancellable, error))
return FALSE;
}
}
else if (!opt_reboot)
{

View File

@ -20,14 +20,18 @@
#include "config.h"
#include <signal.h>
#include <sys/socket.h>
#include <systemd/sd-login.h>
#include <glib-unix.h>
#include <libglnx.h>
#include "rpmostree-dbus-helpers.h"
#include "rpmostree-builtins.h"
#include "rpmostree-libbuiltin.h"
#include "libglnx.h"
#include <sys/socket.h>
#include "glib-unix.h"
#include <signal.h>
#include <systemd/sd-login.h>
#include "rpmostree-util.h"
#include "rpmostree-rpm-util.h"
void
rpmostree_cleanup_peer (GPid *peer_pid)
@ -1154,3 +1158,119 @@ rpmostree_update_deployment (RPMOSTreeOS *os_proxy,
cancellable,
error);
}
static void
append_to_summary (GString *summary,
const char *type,
guint n)
{
if (n == 0)
return;
if (summary->len > 0)
g_string_append (summary, ", ");
g_string_append_printf (summary, "%u %s", n, type);
}
/* this is used by both `status` and `upgrade --check/--preview` */
gboolean
rpmostree_print_cached_update (GVariant *cached_update,
gboolean verbose,
GCancellable *cancellable,
GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Retrieving cached update", error);
g_auto(GVariantDict) dict;
g_variant_dict_init (&dict, cached_update);
/* let's just extract 📤 all the info ahead of time */
const char *checksum;
if (!g_variant_dict_lookup (&dict, "checksum", "&s", &checksum))
return glnx_throw (error, "Missing \"checksum\" key");
const char *version;
if (!g_variant_dict_lookup (&dict, "version", "&s", &version))
version= NULL;
g_autofree char *timestamp = NULL;
{ guint64 t;
if (!g_variant_dict_lookup (&dict, "timestamp", "t", &t))
t = 0;
timestamp = rpmostree_timestamp_str_from_unix_utc (t);
}
gboolean gpg_enabled;
if (!g_variant_dict_lookup (&dict, "gpg-enabled", "b", &gpg_enabled))
gpg_enabled = FALSE;
g_autoptr(GVariant) signatures =
g_variant_dict_lookup_value (&dict, "signatures", G_VARIANT_TYPE ("av"));
gboolean is_new_checksum;
g_assert (g_variant_dict_lookup (&dict, "ref-has-new-commit", "b", &is_new_checksum));
g_autoptr(GVariant) rpm_diff =
g_variant_dict_lookup_value (&dict, "rpm-diff", G_VARIANT_TYPE ("a{sv}"));
/* and now we can print 🖨️ things! */
g_print ("Available update:\n");
/* add the long keys here */
const guint max_key_len = MAX (strlen ("Downgraded"),
strlen ("GPGSignature"));
if (is_new_checksum)
{
rpmostree_print_timestamp_version (version, timestamp, max_key_len);
rpmostree_print_kv ("Commit", max_key_len, checksum);
if (gpg_enabled)
rpmostree_print_gpg_info (signatures, verbose, max_key_len);
}
if (rpm_diff)
{
g_auto(GVariantDict) rpm_diff_dict;
g_variant_dict_init (&rpm_diff_dict, rpm_diff);
g_autoptr(GVariant) upgraded =
_rpmostree_vardict_lookup_value_required (&rpm_diff_dict, "upgraded",
G_VARIANT_TYPE ("a(us(ss)(ss))"), error);
if (!upgraded)
return FALSE;
g_autoptr(GVariant) downgraded =
_rpmostree_vardict_lookup_value_required (&rpm_diff_dict, "downgraded",
G_VARIANT_TYPE ("a(us(ss)(ss))"), error);
if (!downgraded)
return FALSE;
g_autoptr(GVariant) removed =
_rpmostree_vardict_lookup_value_required (&rpm_diff_dict, "removed",
G_VARIANT_TYPE ("a(usss)"), error);
if (!removed)
return FALSE;
g_autoptr(GVariant) added =
_rpmostree_vardict_lookup_value_required (&rpm_diff_dict, "added",
G_VARIANT_TYPE ("a(usss)"), error);
if (!added)
return FALSE;
if (verbose)
rpmostree_variant_diff_print_formatted (max_key_len,
upgraded, downgraded, removed, added);
else
{
g_autoptr(GString) diff_summary = g_string_new (NULL);
append_to_summary (diff_summary, "upgraded", g_variant_n_children (upgraded));
append_to_summary (diff_summary, "downgraded", g_variant_n_children (downgraded));
append_to_summary (diff_summary, "removed", g_variant_n_children (removed));
append_to_summary (diff_summary, "added", g_variant_n_children (added));
rpmostree_print_kv ("Diff", max_key_len, diff_summary->str);
}
}
return TRUE;
}

View File

@ -122,3 +122,9 @@ rpmostree_update_deployment (RPMOSTreeOS *os_proxy,
char **out_transaction_address,
GCancellable *cancellable,
GError **error);
gboolean
rpmostree_print_cached_update (GVariant *cached_update,
gboolean verbose,
GCancellable *cancellable,
GError **error);

View File

@ -50,6 +50,9 @@
<method name="ReloadConfig">
</method>
<!-- none, check, reboot -->
<property name="AutomaticUpdatePolicy" type="s" access="read"/>
<method name="CreateOSName">
<arg type="s" name="name"/>
<arg type="o" name="result" direction="out"/>
@ -77,12 +80,32 @@
'origin' (type 's')
'signatures' (type 'av')
'gpg-enabled' (type 'b')
'ref-has-new-commit' (type 'b')
TRUE if 'checksum' refers to a new commit we're not booted in.
'rpm-diff' (type 'a{sv}')
'upgraded' (type 'a(us(ss)(ss))')
'downgraded' (type 'a(us(ss)(ss))')
'removed' (type 'a(usss)')
'added' (type 'a(usss)')
-->
<property name="CachedUpdate" type="a{sv}" access="read"/>
<property name="HasCachedUpdateRpmDiff" type="b" access="read"/>
<!-- NONE, DIFF, PREPARE, REBOOT -->
<property name="AutomaticUpdatePolicy" type="s" access="read"/>
<!-- Available options:
"mode" (type 's')
One of auto, none, check, reboot. Defaults to
auto, which follows configured policy (available in
AutomaticUpdatePolicy property).
If automatic updates are not enabled, @enabled will be FALSE and
@transaction_address will be the empty string.
-->
<method name="AutomaticUpdateTrigger">
<arg type="a{sv}" name="options" direction="in"/>
<arg type="b" name="enabled" direction="out"/>
<arg type="s" name="transaction_address" direction="out"/>
</method>
<property name="Name" type="s" access="read"/>
<method name="GetDeploymentsRpmDiff">

View File

@ -0,0 +1,8 @@
[Unit]
Description=RPM-OSTree Automatic Update
Documentation=man:rpm-ostree(1) man:rpm-ostreed.conf(5)
ConditionPathExists=/run/ostree-booted
[Service]
Type=oneshot
ExecStart=@bindir@/rpm-ostree upgrade --automatic

View File

@ -0,0 +1,11 @@
[Unit]
Description=RPM-OSTree Automatic Update Trigger
Documentation=man:rpm-ostree(1) man:rpm-ostreed.conf(5)
ConditionPathExists=/run/ostree-booted
[Timer]
OnBootSec=1h
OnUnitInactiveSec=1d
[Install]
WantedBy=timers.target

View File

@ -3,4 +3,5 @@
# For option meanings, see rpm-ostreed.conf(5).
[Daemon]
#AutomaticUpdatePolicy=none
#IdleExitTimeout=60

View File

@ -22,6 +22,7 @@
#include "rpmostreed-sysroot.h"
#include "rpmostreed-types.h"
#include "rpmostreed-utils.h"
#include "rpmostree-util.h"
#include <libglnx.h>
#include <systemd/sd-journal.h>
@ -61,8 +62,9 @@ struct _RpmostreedDaemon {
RpmostreedSysroot *sysroot;
gchar *sysroot_path;
/* we only have one setting for now, so let's just keep it in the main struct */
/* we only have two settings for now, so let's just keep it in the main struct */
guint idle_exit_timeout;
RpmostreedAutomaticUpdatePolicy auto_update_policy;
GDBusConnection *connection;
GDBusObjectManagerServer *object_manager;
@ -312,6 +314,17 @@ maybe_load_config_keyfile (GKeyFile **out_keyfile,
return TRUE;
}
static char*
get_config_str (GKeyFile *keyfile,
const char *key,
const char *default_val)
{
g_autofree char *val = NULL;
if (keyfile)
val = g_key_file_get_string (keyfile, DAEMON_CONFIG_GROUP, key, NULL);
return g_steal_pointer (&val) ?: g_strdup (default_val);
}
static guint64
get_config_uint64 (GKeyFile *keyfile,
const char *key,
@ -330,6 +343,20 @@ get_config_uint64 (GKeyFile *keyfile,
return default_val;
}
RpmostreedAutomaticUpdatePolicy
rpmostreed_get_automatic_update_policy (RpmostreedDaemon *self)
{
return self->auto_update_policy;
}
/* in-place version of g_ascii_strdown */
static inline void
ascii_strdown_inplace (char *str)
{
for (char *c = str; *c; c++)
*c = g_ascii_tolower (*c);
}
gboolean
rpmostreed_daemon_reload_config (RpmostreedDaemon *self,
gboolean *out_changed,
@ -343,12 +370,32 @@ rpmostreed_daemon_reload_config (RpmostreedDaemon *self,
* follow-up requests are more responsive */
guint64 idle_exit_timeout = get_config_uint64 (config, "IdleExitTimeout", 60);
/* default to off for now; we will change it to "check" in a later release */
RpmostreedAutomaticUpdatePolicy auto_update_policy =
RPMOSTREED_AUTOMATIC_UPDATE_POLICY_NONE;
g_autofree char *auto_update_policy_str =
get_config_str (config, "AutomaticUpdatePolicy", NULL);
if (auto_update_policy_str)
{
ascii_strdown_inplace (auto_update_policy_str);
if (!rpmostree_str_to_auto_update_policy (auto_update_policy_str,
&auto_update_policy, error))
return FALSE;
}
/* don't update changed for this; it's contained to RpmostreedDaemon so no other objects
* need to be reloaded if it changes */
self->idle_exit_timeout = idle_exit_timeout;
gboolean changed = FALSE;
changed = changed || (self->auto_update_policy != auto_update_policy);
self->auto_update_policy = auto_update_policy;
if (out_changed)
*out_changed = FALSE;
*out_changed = changed;
return TRUE;
}

View File

@ -19,6 +19,7 @@
#pragma once
#include "rpmostreed-types.h"
#include "rpmostree-util.h"
#define RPMOSTREED_TYPE_DAEMON (rpmostreed_daemon_get_type ())
#define RPMOSTREED_DAEMON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RPMOSTREED_TYPE_DAEMON, RpmostreedDaemon))
@ -50,3 +51,6 @@ void rpmostreed_daemon_unpublish (RpmostreedDaemon *self,
gboolean rpmostreed_daemon_reload_config (RpmostreedDaemon *self,
gboolean *out_changed,
GError **error);
RpmostreedAutomaticUpdatePolicy
rpmostreed_get_automatic_update_policy (RpmostreedDaemon *self);

View File

@ -18,15 +18,19 @@
#include "config.h"
#include <systemd/sd-journal.h>
#include <libglnx.h>
#include "rpmostreed-deployment-utils.h"
#include "rpmostree-origin.h"
#include "rpmostree-util.h"
#include "rpmostree-rpm-util.h"
#include "rpmostree-sysroot-core.h"
#include "rpmostree-core.h"
#include "rpmostree-package-variants.h"
#include "rpmostreed-utils.h"
#include "rpmostreed-errors.h"
#include <libglnx.h>
/* Get a currently unique (for this host) identifier for the
* deployment; TODO - adding the deployment timestamp would make it
* persistently unique, needs API in libostree.
@ -187,7 +191,7 @@ variant_add_metadata_attribute (GVariantDict *dict,
static void
variant_add_commit_details (GVariantDict *dict,
const char *prefix,
const char *prefix,
GVariant *commit)
{
g_autoptr(GVariant) metadata = NULL;
@ -437,3 +441,454 @@ rpmostreed_commit_generate_cached_details_variant (OstreeDeployment *deployment,
return g_variant_ref_sink (g_variant_dict_end (&dict));
}
typedef struct {
gboolean initialized;
GPtrArray *upgraded;
GPtrArray *downgraded;
GPtrArray *removed;
GPtrArray *added;
} RpmDiff;
static void
rpm_diff_init (RpmDiff *diff)
{
g_assert (!diff->initialized);
diff->upgraded = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
diff->downgraded = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
diff->removed = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
diff->added = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
diff->initialized = TRUE;
}
static void
rpm_diff_clear (RpmDiff *diff)
{
if (!diff->initialized)
return;
g_clear_pointer (&diff->upgraded, (GDestroyNotify)g_ptr_array_unref);
g_clear_pointer (&diff->downgraded, (GDestroyNotify)g_ptr_array_unref);
g_clear_pointer (&diff->removed, (GDestroyNotify)g_ptr_array_unref);
g_clear_pointer (&diff->added, (GDestroyNotify)g_ptr_array_unref);
diff->initialized = FALSE;
}
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (RpmDiff, rpm_diff_clear);
static GVariant*
single_pkg_variant_new (RpmOstreePkgTypes type,
RpmOstreePackage *pkg)
{
return g_variant_ref_sink (
g_variant_new ("(usss)", type,
rpm_ostree_package_get_name (pkg),
rpm_ostree_package_get_evr (pkg),
rpm_ostree_package_get_arch (pkg)));
}
static GVariant*
modified_pkg_variant_new (RpmOstreePkgTypes type,
RpmOstreePackage *pkg_old,
RpmOstreePackage *pkg_new)
{
const char *name_old = rpm_ostree_package_get_name (pkg_old);
const char *name_new = rpm_ostree_package_get_name (pkg_new);
g_assert_cmpstr (name_old, ==, name_new);
return g_variant_ref_sink (
g_variant_new ("(us(ss)(ss))", type, name_old,
rpm_ostree_package_get_evr (pkg_old),
rpm_ostree_package_get_arch (pkg_old),
rpm_ostree_package_get_evr (pkg_new),
rpm_ostree_package_get_arch (pkg_new)));
}
static GVariant*
modified_dnfpkg_variant_new (RpmOstreePkgTypes type,
RpmOstreePackage *pkg_old,
DnfPackage *pkg_new)
{
const char *name_old = rpm_ostree_package_get_name (pkg_old);
const char *name_new = dnf_package_get_name (pkg_new);
g_assert_cmpstr (name_old, ==, name_new);
return g_variant_ref_sink (
g_variant_new ("(us(ss)(ss))", type, name_old,
rpm_ostree_package_get_evr (pkg_old),
rpm_ostree_package_get_arch (pkg_old),
dnf_package_get_evr (pkg_new),
dnf_package_get_arch (pkg_new)));
}
static void
rpm_diff_add_base_db_diff (RpmDiff *diff,
/* element-type RpmOstreePackage */
GPtrArray *removed,
GPtrArray *added,
GPtrArray *modified_old,
GPtrArray *modified_new)
{
g_assert_cmpuint (modified_old->len, ==, modified_new->len);
RpmOstreePkgTypes type = RPM_OSTREE_PKG_TYPE_BASE;
for (guint i = 0; i < removed->len; i++)
g_ptr_array_add (diff->removed, single_pkg_variant_new (type, removed->pdata[i]));
for (guint i = 0; i < added->len; i++)
g_ptr_array_add (diff->added, single_pkg_variant_new (type, added->pdata[i]));
for (guint i = 0; i < modified_old->len; i++)
{
RpmOstreePackage *old_pkg = modified_old->pdata[i];
RpmOstreePackage *new_pkg = modified_new->pdata[i];
if (rpm_ostree_package_cmp (old_pkg, new_pkg) < 0)
g_ptr_array_add (diff->upgraded,
modified_pkg_variant_new (type, old_pkg, new_pkg));
else
g_ptr_array_add (diff->downgraded,
modified_pkg_variant_new (type, old_pkg, new_pkg));
}
}
static void
rpm_diff_add_layered_diff (RpmDiff *diff,
RpmOstreePackage *old_pkg,
DnfPackage *new_pkg)
{
/* add to upgraded; layered pkgs only go up */
RpmOstreePkgTypes type = RPM_OSTREE_PKG_TYPE_LAYER;
g_ptr_array_add (diff->upgraded, modified_dnfpkg_variant_new (type, old_pkg, new_pkg));
}
static int
sort_pkgvariant_by_name (gconstpointer pkga_pp,
gconstpointer pkgb_pp)
{
GVariant *pkg_a = *((GVariant**)pkga_pp);
GVariant *pkg_b = *((GVariant**)pkgb_pp);
const char *pkgname_a;
g_variant_get_child (pkg_a, 1, "&s", &pkgname_a);
const char *pkgname_b;
g_variant_get_child (pkg_b, 1, "&s", &pkgname_b);
return strcmp (pkgname_a, pkgname_b);
}
static GVariant*
array_to_variant_new (const char *format, GPtrArray *array)
{
if (array->len == 0)
return g_variant_new (format, NULL);
/* make doubly sure it's sorted */
g_ptr_array_sort (array, sort_pkgvariant_by_name);
g_auto(GVariantBuilder) builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
for (guint i = 0; i < array->len; i++)
g_variant_builder_add_value (&builder, array->pdata[i]);
return g_variant_builder_end (&builder);
}
static GVariant*
rpm_diff_variant_new (RpmDiff *diff)
{
g_assert (diff->initialized);
g_auto(GVariantDict) dict;
g_variant_dict_init (&dict, NULL);
g_variant_dict_insert_value (&dict, "upgraded",
array_to_variant_new ("a(us(ss)(ss))", diff->upgraded));
g_variant_dict_insert_value (&dict, "downgraded",
array_to_variant_new ("a(us(ss)(ss))", diff->downgraded));
g_variant_dict_insert_value (&dict, "removed",
array_to_variant_new ("a(usss)", diff->removed));
g_variant_dict_insert_value (&dict, "added",
array_to_variant_new ("a(usss)", diff->added));
return g_variant_dict_end (&dict);
}
static DnfPackage*
find_newer_package (DnfSack *sack,
RpmOstreePackage *pkg)
{
hy_autoquery HyQuery query = hy_query_create (sack);
hy_query_filter (query, HY_PKG_NAME, HY_EQ, rpm_ostree_package_get_name (pkg));
hy_query_filter (query, HY_PKG_EVR, HY_GT, rpm_ostree_package_get_evr (pkg));
hy_query_filter (query, HY_PKG_ARCH, HY_NEQ, "src");
hy_query_filter_latest (query, TRUE);
g_autoptr(GPtrArray) new_pkgs = hy_query_run (query);
if (new_pkgs->len == 0)
return NULL; /* canonicalize to NULL */
g_ptr_array_sort (new_pkgs, (GCompareFunc)rpmostree_pkg_array_compare);
return g_object_ref (new_pkgs->pdata[new_pkgs->len-1]);
}
/* For all layered pkgs, check if there are newer versions in the rpmmd. Add diff to
* @rpm_diff, and all new pkgs in @out_newer_packages (these are used later for advisories).
* */
static gboolean
rpmmd_diff (OstreeSysroot *sysroot,
/* these are just to avoid refetching them */
OstreeRepo *repo,
OstreeDeployment *deployment,
const char *base_checksum,
DnfSack *sack,
RpmDiff *rpm_diff,
GPtrArray **out_newer_packages,
GError **error)
{
/* Note here that we *don't* actually use layered_pkgs; we want to look at all the RPMs
* installed, whereas the layered pkgs (actually patterns) just represent top-level
* entries. IOW, we want to run through all layered RPMs, which include deps of
* layered_pkgs. */
g_autoptr(GPtrArray) all_layered_pkgs = NULL;
const char *layered_checksum = ostree_deployment_get_csum (deployment);
RpmOstreeDbDiffExtFlags flags = RPM_OSTREE_DB_DIFF_EXT_ALLOW_NOENT;
if (!rpm_ostree_db_diff_ext (repo, base_checksum, layered_checksum, flags, NULL,
&all_layered_pkgs, NULL, NULL, NULL, error))
return FALSE;
/* XXX: need to filter out local pkgs; though we still want to check for advisories --
* maybe we should do this in status.c instead? */
if (all_layered_pkgs == NULL || /* -> older layer before we injected pkglist metadata */
all_layered_pkgs->len == 0) /* -> no layered pkgs, e.g. override remove only */
{
*out_newer_packages = NULL;
return TRUE; /* note early return */
}
/* for each layered pkg, check if there's a newer version available (in reality, there may
* be other new pkgs that need to be layered or some pkgs that no longer need to, but we
* won't find out until we have the full commit available -- XXX: we could go the extra
* effort and use the rpmdb of new_checksum if we already have it somehow, though that's
* probably not the common case */
g_autoptr(GPtrArray) newer_packages =
g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
for (guint i = 0; i < all_layered_pkgs->len; i++)
{
RpmOstreePackage *pkg = all_layered_pkgs->pdata[i];
g_autoptr(DnfPackage) newer_pkg = find_newer_package (sack, pkg);
if (!newer_pkg)
continue;
g_ptr_array_add (newer_packages, g_object_ref (newer_pkg));
rpm_diff_add_layered_diff (rpm_diff, pkg, newer_pkg);
}
/* canonicalize to NULL if there's nothing new */
if (newer_packages->len == 0)
g_clear_pointer (&newer_packages, (GDestroyNotify)g_ptr_array_unref);
*out_newer_packages = g_steal_pointer (&newer_packages);
return TRUE;
}
static gboolean
get_cached_rpmmd_sack (OstreeSysroot *sysroot,
OstreeRepo *repo,
OstreeDeployment *deployment,
DnfSack **out_sack,
GError **error)
{
/* we don't need the full force of the core ctx here; we just want a DnfContext so that it
* can load the repos and deal with releasever for us */
g_autoptr(DnfContext) ctx = dnf_context_new ();
/* We have to point to the same source root for releasever to hit the right cache: an
* interesting point here is that if there's a newer $releasever pending (i.e. 'deploy'
* auto update policy), we'll still be using the previous releasever -- this is OK though,
* we should be special-casing these rebases later re. how to display them; at least
* status already shows endoflife. See also deploy_transaction_execute(). */
g_autofree char *deployment_root = rpmostree_get_deployment_root (sysroot, deployment);
dnf_context_set_source_root (ctx, deployment_root);
g_autofree char *reposdir = g_build_filename (deployment_root, "etc/yum.repos.d", NULL);
dnf_context_set_repo_dir (ctx, reposdir);
dnf_context_set_cache_dir (ctx, RPMOSTREE_CORE_CACHEDIR RPMOSTREE_DIR_CACHE_REPOMD);
dnf_context_set_solv_dir (ctx, RPMOSTREE_CORE_CACHEDIR RPMOSTREE_DIR_CACHE_SOLV);
if (!dnf_context_setup (ctx, NULL, error))
return FALSE;
/* add the repos but strictly from cache; we should have already *just* checked &
* refreshed metadata as part of the DeployTransaction; but we gracefully handle bad cache
* too (e.g. if we start using the new dnf_context_clean_cache() on rebases?) */
GPtrArray *repos = dnf_context_get_repos (ctx);
/* need a new DnfSackAddFlags flag for dnf_sack_add_repos to say "don't fallback to
* updating if cache invalid/absent"; for now, just do it ourselves */
GPtrArray *cached_enabled_repos = g_ptr_array_new ();
for (guint i = 0; i < repos->len; i++)
{
DnfRepo *repo = repos->pdata[i];
if ((dnf_repo_get_enabled (repo) & DNF_REPO_ENABLED_PACKAGES) == 0)
continue;
/* TODO: We need to expand libdnf here to somehow do a dnf_repo_check() without it
* triggering a download if there's no cache at all. Here, we just physically check
* for the location. */
const char *location = dnf_repo_get_location (repo);
if (!glnx_fstatat_allow_noent (AT_FDCWD, location, NULL, 0, error))
return FALSE;
if (errno == ENOENT)
continue;
g_autoptr(GError) local_error = NULL;
g_autoptr(DnfState) state = dnf_state_new ();
if (!dnf_repo_check (repo, G_MAXUINT, state, &local_error))
sd_journal_print (LOG_WARNING, "Couldn't load cache for repo %s: %s",
dnf_repo_get_id (repo), local_error->message);
else
g_ptr_array_add (cached_enabled_repos, repo);
}
g_autoptr(DnfSack) sack = NULL;
if (cached_enabled_repos->len > 0)
{
/* Set up our own sack and point it to the solv cache. TODO: We could've used the sack
* from dnf_context_setup_sack(), but we need to extend libdnf to specify flags like
* UPDATEINFO beforehand. Otherwise we have to add_repos() twice which almost double
* startup time. */
sack = dnf_sack_new ();
dnf_sack_set_cachedir (sack, RPMOSTREE_CORE_CACHEDIR RPMOSTREE_DIR_CACHE_SOLV);
if (!dnf_sack_setup (sack, DNF_SACK_SETUP_FLAG_MAKE_CACHE_DIR, error))
return FALSE;
/* we still use add_repos rather than add_repo separately above because it does nice
* things like process excludes */
g_autoptr(DnfState) state = dnf_state_new ();
if (!dnf_sack_add_repos (sack, cached_enabled_repos, G_MAXUINT,
DNF_SACK_ADD_FLAG_UPDATEINFO, state, error))
return FALSE;
}
*out_sack = g_steal_pointer (&sack);
return TRUE;
}
/* The variant returned by this function is backwards compatible with the one returned by
* rpmostreed_commit_generate_cached_details_variant(). However, it also includes a base
* tree db diff, layered pkgs diff, state, advisories, etc... Also, it will happily return
* NULL if no updates are available. */
gboolean
rpmostreed_update_generate_variant (OstreeSysroot *sysroot,
OstreeDeployment *deployment,
OstreeRepo *repo,
GVariant **out_update,
GError **error)
{
/* We try to minimize I/O in this function. We're in the daemon startup path, and thus
* directly contribute to lag from a cold `rpm-ostree status`. Anyway, as a principle we
* shouldn't do long-running operations outside of transactions. */
g_autoptr(RpmOstreeOrigin) origin = rpmostree_origin_parse_deployment (deployment, error);
if (!origin)
return FALSE;
const char *refspec = rpmostree_origin_get_refspec (origin);
{ RpmOstreeRefspecType refspectype = RPMOSTREE_REFSPEC_TYPE_OSTREE;
const char *refspec_data;
if (!rpmostree_refspec_classify (refspec, &refspectype, &refspec_data, error))
return FALSE;
/* we don't support jigdo-based origins yet */
if (refspectype != RPMOSTREE_REFSPEC_TYPE_OSTREE)
{
*out_update = NULL;
return TRUE; /* NB: early return */
}
/* just skip over "ostree://" so we can talk with libostree without thinking about it */
refspec = refspec_data;
}
/* let's start with the ostree side of things */
g_autofree char *new_checksum = NULL;
if (!ostree_repo_resolve_rev_ext (repo, refspec, TRUE, 0, &new_checksum, error))
return FALSE;
const char *current_checksum = ostree_deployment_get_csum (deployment);
gboolean is_layered;
g_autofree char *current_checksum_owned = NULL;
if (!rpmostree_deployment_get_layered_info (repo, deployment, &is_layered,
&current_checksum_owned, NULL, NULL, NULL,
error))
return FALSE;
if (is_layered)
current_checksum = current_checksum_owned;
/* Graciously handle rev no longer in repo; e.g. mucking around with rebase/rollback; we
* still want to do the rpm-md phase. In that case, just use the current csum. */
gboolean is_new_checksum = FALSE;
if (!new_checksum)
new_checksum = g_strdup (current_checksum);
else
is_new_checksum = !g_str_equal (new_checksum, current_checksum);
g_autoptr(GVariant) commit = NULL;
if (!ostree_repo_load_commit (repo, new_checksum, &commit, NULL, error))
return FALSE;
g_auto(GVariantDict) dict;
g_variant_dict_init (&dict, NULL);
/* first get all the traditional/backcompat stuff */
if (!add_all_commit_details_to_vardict (deployment, repo, refspec,
new_checksum, commit, &dict, error))
return FALSE;
/* This may seem trivial, but it's important to keep the final variant as self-contained
* and "diff-based" as possible, since it'll be available as a D-Bus property. This makes
* it easier to consume for UIs like GNOME Software and Cockpit. */
g_variant_dict_insert (&dict, "ref-has-new-commit", "b", is_new_checksum);
g_auto(RpmDiff) rpm_diff = {0, };
rpm_diff_init (&rpm_diff);
/* we'll need this later for advisories, so just keep it around */
g_autoptr(GPtrArray) ostree_modified_new = NULL;
if (is_new_checksum)
{
g_autoptr(GPtrArray) removed = NULL;
g_autoptr(GPtrArray) added = NULL;
g_autoptr(GPtrArray) modified_old = NULL;
/* Note we allow_noent here; we'll just skip over the rpm diff if there's no data */
RpmOstreeDbDiffExtFlags flags = RPM_OSTREE_DB_DIFF_EXT_ALLOW_NOENT;
if (!rpm_ostree_db_diff_ext (repo, current_checksum, new_checksum, flags, &removed,
&added, &modified_old, &ostree_modified_new, NULL, error))
return FALSE;
/* check if allow_noent kicked in */
if (removed)
rpm_diff_add_base_db_diff (&rpm_diff, removed, added,
modified_old, ostree_modified_new);
}
/* now we look at the rpm-md side */
/* first we try to set up a sack (NULL --> no cache available) */
g_autoptr(DnfSack) sack = NULL;
if (!get_cached_rpmmd_sack (sysroot, repo, deployment, &sack, error))
return FALSE;
g_autoptr(GPtrArray) rpmmd_modified_new = NULL;
GHashTable *layered_pkgs = rpmostree_origin_get_packages (origin);
/* check that it's actually layered (i.e. the requests are not all just dormant) */
if (sack && is_layered && g_hash_table_size (layered_pkgs) > 0)
{
if (!rpmmd_diff (sysroot, repo, deployment, current_checksum, sack, &rpm_diff,
&rpmmd_modified_new, error))
return FALSE;
}
g_variant_dict_insert (&dict, "rpm-diff", "@a{sv}", rpm_diff_variant_new (&rpm_diff));
/* but if there are no updates, then just ditch the whole thing and return NULL */
if (is_new_checksum || rpmmd_modified_new)
*out_update = g_variant_ref_sink (g_variant_dict_end (&dict));
else
*out_update = NULL;
return TRUE;
}

View File

@ -44,3 +44,9 @@ GVariant * rpmostreed_commit_generate_cached_details_variant (OstreeDeploym
OstreeRepo *repo,
const gchar *refspec,
GError **error);
gboolean
rpmostreed_update_generate_variant (OstreeSysroot *sysroot,
OstreeDeployment *deployment,
OstreeRepo *repo,
GVariant **out_update,
GError **error);

View File

@ -20,6 +20,7 @@
#include "ostree.h"
#include <libglnx.h>
#include <systemd/sd-journal.h>
#include "rpmostreed-sysroot.h"
#include "rpmostreed-daemon.h"
@ -119,7 +120,9 @@ os_authorize_method (GDBusInterfaceSkeleton *interface,
{
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.deploy");
}
else if (g_strcmp0 (method_name, "Upgrade") == 0)
/* unite these for now; it could make sense at least to make "check" its own action */
else if (g_strcmp0 (method_name, "Upgrade") == 0 ||
g_strcmp0 (method_name, "AutomaticUpdateTrigger") == 0)
{
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.upgrade");
}
@ -713,6 +716,20 @@ start_deployment_txn (GDBusMethodInvocation *invocation,
cancellable, error);
}
static gboolean
refresh_cached_update (RpmostreedOS*, GError **error);
static void
on_auto_update_done (RpmostreedTransaction *transaction, RpmostreedOS *self)
{
g_autoptr(GError) local_error = NULL;
if (!refresh_cached_update (self, &local_error))
{
sd_journal_print (LOG_WARNING, "Failed to refresh CachedUpdate property: %s",
local_error->message);
}
}
typedef void (*InvocationCompleter)(RPMOSTreeOS*,
GDBusMethodInvocation*,
GUnixFDList*,
@ -772,8 +789,15 @@ os_merge_or_start_deployment_txn (RPMOSTreeOS *interface,
fd_list,
&local_error);
if (transaction)
rpmostreed_transaction_monitor_add (self->transaction_monitor,
transaction);
rpmostreed_transaction_monitor_add (self->transaction_monitor, transaction);
/* For the AutomaticUpdateTrigger "check" and "download" cases, we want to make sure
* we refresh CachedUpdate after; "deploy" will do this through sysroot_changed */
const char *method_name = g_dbus_method_invocation_get_method_name (invocation);
if (g_str_equal (method_name, "AutomaticUpdateTrigger") &&
(default_flags & (RPMOSTREE_TRANSACTION_DEPLOY_FLAG_DOWNLOAD_ONLY |
RPMOSTREE_TRANSACTION_DEPLOY_FLAG_DOWNLOAD_METADATA_ONLY)))
g_signal_connect (transaction, "closed", G_CALLBACK (on_auto_update_done), self);
}
if (transaction)
@ -892,6 +916,75 @@ os_handle_update_deployment (RPMOSTreeOS *interface,
rpmostree_os_complete_update_deployment);
}
/* compat shim for call completer */
static void automatic_update_trigger_completer (RPMOSTreeOS *os,
GDBusMethodInvocation *invocation,
GUnixFDList *dummy,
const gchar *address)
{ /* enabled */
rpmostree_os_complete_automatic_update_trigger (os, invocation, TRUE, address);
}
/* we make this a separate method to keep the D-Bus API clean, but the actual
* implementation is done by our dear friend deploy_transaction_execute().
*/
static gboolean
os_handle_automatic_update_trigger (RPMOSTreeOS *interface,
GDBusMethodInvocation *invocation,
GVariant *arg_options)
{
g_auto(GVariantDict) dict;
g_variant_dict_init (&dict, arg_options);
const char *mode = vardict_lookup_ptr (&dict, "mode", "&s") ?: "auto";
g_autoptr(GError) local_error = NULL;
GError **error = &local_error;
RpmostreedAutomaticUpdatePolicy autoupdate_policy;
if (g_str_equal (mode, "auto"))
autoupdate_policy = rpmostreed_get_automatic_update_policy (rpmostreed_daemon_get ());
else
{
if (!rpmostree_str_to_auto_update_policy (mode, &autoupdate_policy, error))
{
g_dbus_method_invocation_take_error (invocation, g_steal_pointer (&local_error));
return TRUE;
}
}
/* Now we translate policy into flags the deploy transaction understands. But avoid
* starting it at all if we're not even on. The benefit of this approach is that we keep
* the Deploy transaction simpler. */
RpmOstreeTransactionDeployFlags dfault = 0;
switch (autoupdate_policy)
{
case RPMOSTREED_AUTOMATIC_UPDATE_POLICY_NONE:
{
/* NB: we return the empty string here rather than NULL, because gdbus converts this
* to a gvariant, which doesn't support NULL strings */ /* enabled */
rpmostree_os_complete_automatic_update_trigger (interface, invocation, FALSE, "");
return TRUE;
}
case RPMOSTREED_AUTOMATIC_UPDATE_POLICY_CHECK:
dfault = RPMOSTREE_TRANSACTION_DEPLOY_FLAG_DOWNLOAD_METADATA_ONLY;
break;
default:
g_assert_not_reached ();
}
return os_merge_or_start_deployment_txn (
interface,
invocation,
dfault,
NULL,
NULL,
NULL,
automatic_update_trigger_completer);
}
static gboolean
os_handle_rollback (RPMOSTreeOS *interface,
GDBusMethodInvocation *invocation,
@ -1596,6 +1689,39 @@ out:
return TRUE;
}
static gboolean
refresh_cached_update (RpmostreedOS *self, GError **error)
{
const char *name = rpmostree_os_get_name (RPMOSTREE_OS (self));
OstreeSysroot *sysroot = rpmostreed_sysroot_get_root (rpmostreed_sysroot_get ());
OstreeRepo *repo = ostree_sysroot_repo (sysroot);
/* if we're not handling the system sysroot, then let's just skip all this (e.g. `make
* check` tests) */
const char *sysroot_path = gs_file_get_path_cached (ostree_sysroot_get_path (sysroot));
if (!g_str_equal (sysroot_path, "/"))
return TRUE;
/* Note here we're *not* using rpmostree_syscore_get_origin_merge_deployment(): cached
* updates are always relative to the booted/merge deployment; e.g. we still want to be
* able to show details about a pending deployment. */
g_autoptr(OstreeDeployment) merge_deployment =
ostree_sysroot_get_merge_deployment (sysroot, name);
g_autoptr(GVariant) cached_update = NULL;
if (!rpmostreed_update_generate_variant (sysroot, merge_deployment, repo,
&cached_update, error))
return FALSE;
rpmostree_os_set_cached_update (RPMOSTREE_OS (self), cached_update);
/* for backwards compatibility */
gboolean has_cached_updates = (cached_update != NULL);
rpmostree_os_set_has_cached_update_rpm_diff (RPMOSTREE_OS (self), has_cached_updates);
return TRUE;
}
static gboolean
rpmostreed_os_load_internals (RpmostreedOS *self, GError **error)
{
@ -1603,16 +1729,12 @@ rpmostreed_os_load_internals (RpmostreedOS *self, GError **error)
OstreeDeployment *booted = NULL; /* owned by sysroot */
g_autofree gchar* booted_id = NULL;
glnx_unref_object OstreeDeployment *merge_deployment = NULL; /* transfered */
g_autoptr(GPtrArray) deployments = NULL;
OstreeSysroot *ot_sysroot;
OstreeRepo *ot_repo;
GVariant *booted_variant = NULL;
GVariant *default_variant = NULL;
GVariant *rollback_variant = NULL;
g_autoptr(GVariant) cached_update = NULL;
gboolean has_cached_updates = FALSE;
name = rpmostree_os_get_name (RPMOSTREE_OS (self));
g_debug ("loading %s", name);
@ -1661,25 +1783,6 @@ rpmostreed_os_load_internals (RpmostreedOS *self, GError **error)
}
}
merge_deployment = ostree_sysroot_get_merge_deployment (ot_sysroot, name);
if (merge_deployment)
{
g_autoptr(RpmOstreeOrigin) origin = NULL;
/* Don't fail here for unknown origin types */
origin = rpmostree_origin_parse_deployment (merge_deployment, NULL);
if (origin)
{
cached_update = rpmostreed_commit_generate_cached_details_variant (merge_deployment,
ot_repo,
rpmostree_origin_get_refspec (origin),
error);
if (!cached_update)
return FALSE;
has_cached_updates = cached_update != NULL;
}
}
if (!booted_variant)
booted_variant = rpmostreed_deployment_generate_blank_variant ();
rpmostree_os_set_booted_deployment (RPMOSTREE_OS (self),
@ -1695,10 +1798,10 @@ rpmostreed_os_load_internals (RpmostreedOS *self, GError **error)
rpmostree_os_set_rollback_deployment (RPMOSTREE_OS (self),
rollback_variant);
rpmostree_os_set_cached_update (RPMOSTREE_OS (self), cached_update);
rpmostree_os_set_has_cached_update_rpm_diff (RPMOSTREE_OS (self),
has_cached_updates);
g_dbus_interface_skeleton_flush(G_DBUS_INTERFACE_SKELETON (self));
if (!refresh_cached_update (self, error))
return FALSE;
g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (self));
return TRUE;
}
@ -1706,6 +1809,7 @@ rpmostreed_os_load_internals (RpmostreedOS *self, GError **error)
static void
rpmostreed_os_iface_init (RPMOSTreeOSIface *iface)
{
iface->handle_automatic_update_trigger = os_handle_automatic_update_trigger;
iface->handle_cleanup = os_handle_cleanup;
iface->handle_get_deployment_boot_config = os_handle_get_deployment_boot_config;
iface->handle_kernel_args = os_handle_kernel_args;

View File

@ -409,6 +409,20 @@ handle_unregister_client (RPMOSTreeSysroot *object,
return TRUE;
}
static gboolean
reset_config_properties (RpmostreedSysroot *self,
GError **error)
{
RpmostreedDaemon *daemon = rpmostreed_daemon_get ();
RpmostreedAutomaticUpdatePolicy policy = rpmostreed_get_automatic_update_policy (daemon);
const char *policy_str = rpmostree_auto_update_policy_to_str (policy, NULL);
g_assert (policy_str);
rpmostree_sysroot_set_automatic_update_policy (RPMOSTREE_SYSROOT (self), policy_str);
return TRUE;
}
static gboolean
handle_reload_config (RPMOSTreeSysroot *object,
GDBusMethodInvocation *invocation)
@ -417,7 +431,11 @@ handle_reload_config (RPMOSTreeSysroot *object,
g_autoptr(GError) local_error = NULL;
GError **error = &local_error;
if (!rpmostreed_daemon_reload_config (rpmostreed_daemon_get (), NULL, error))
gboolean changed = FALSE;
if (!rpmostreed_daemon_reload_config (rpmostreed_daemon_get (), &changed, error))
goto out;
if (changed && !reset_config_properties (self, error))
goto out;
if (!rpmostreed_sysroot_reload (self, error))
@ -742,6 +760,9 @@ rpmostreed_sysroot_populate (RpmostreedSysroot *self,
if (!sysroot_populate_deployments_unlocked (self, NULL, error))
return FALSE;
if (!reset_config_properties (self, error))
return FALSE;
if (self->monitor == NULL)
{
const char *sysroot_path = gs_file_get_path_cached (ostree_sysroot_get_path (self->ot_sysroot));

View File

@ -176,6 +176,9 @@ package_diff_transaction_execute (RpmostreedTransaction *transaction,
GCancellable *cancellable,
GError **error)
{
/* XXX: we should just unify this with deploy_transaction_execute to take advantage of the
* new pkglist metadata when possible */
PackageDiffTransaction *self = (PackageDiffTransaction *) transaction;
RpmOstreeSysrootUpgraderFlags upgrader_flags = 0;
@ -598,6 +601,10 @@ deploy_transaction_execute (RpmostreedTransaction *transaction,
/* Mainly for the `install` and `override` commands */
const gboolean no_pull_base =
((self->flags & RPMOSTREE_TRANSACTION_DEPLOY_FLAG_NO_PULL_BASE) > 0);
/* Used to background check for updates; this essentially means downloading the minimum
* amount of metadata only to check if there's an upgrade */
const gboolean download_metadata_only =
((self->flags & RPMOSTREE_TRANSACTION_DEPLOY_FLAG_DOWNLOAD_METADATA_ONLY) > 0);
RpmOstreeSysrootUpgraderFlags upgrader_flags = 0;
if (self->flags & RPMOSTREE_TRANSACTION_DEPLOY_FLAG_ALLOW_DOWNGRADE)
@ -605,6 +612,11 @@ deploy_transaction_execute (RpmostreedTransaction *transaction,
if (dry_run)
upgrader_flags |= RPMOSTREE_SYSROOT_UPGRADER_FLAGS_DRY_RUN;
/* DOWNLOAD_METADATA_ONLY isn't directly exposed at the D-Bus API level, so we shouldn't
* ever run into these conflicting options */
if (download_metadata_only)
g_assert (!(no_pull_base || cache_only || download_only));
if (cache_only)
{
/* practically, we could unite those two into a single flag, though it's nice to be
@ -701,8 +713,11 @@ deploy_transaction_execute (RpmostreedTransaction *transaction,
else
g_string_append (txn_title, "upgrade");
/* so users know we were probably fired by the automated timer when looking at status */
if (cache_only)
g_string_append (txn_title, " (cache only)");
else if (download_metadata_only)
g_string_append (txn_title, " (check only)");
else if (download_only)
g_string_append (txn_title, " (download only)");
@ -865,7 +880,11 @@ deploy_transaction_execute (RpmostreedTransaction *transaction,
{
gboolean base_changed;
if (!rpmostree_sysroot_upgrader_pull_base (upgrader, NULL, 0, progress,
OstreeRepoPullFlags flags = OSTREE_REPO_PULL_FLAGS_NONE;
if (download_metadata_only)
flags |= OSTREE_REPO_PULL_FLAGS_COMMIT_ONLY;
if (!rpmostree_sysroot_upgrader_pull_base (upgrader, NULL, flags, progress,
&base_changed, cancellable, error))
return FALSE;
@ -933,6 +952,48 @@ deploy_transaction_execute (RpmostreedTransaction *transaction,
changed = TRUE;
}
if (download_metadata_only)
{
/* We have to short-circuit the usual path here; we already downloaded the ostree
* metadata, so now we just need to update the rpmmd data (but only if we actually
* have pkgs layered). This is still just a heuristic, since e.g. an InactiveRequest
* may in fact become active in the new base, but we don't have the full tree. */
/* XXX: in jigdo mode we'll want to do this unconditionally */
if (g_hash_table_size (rpmostree_origin_get_packages (origin)) > 0)
{
/* XXX: dedupe a bit more with RefreshMd path */
g_autoptr(RpmOstreeContext) ctx =
rpmostree_context_new_system (repo, cancellable, error);
/* Note here that we use the cfg merge deployment for releasever: the download
* metadata only path is currently used only by the auto-update checker, and there
* we want to show updates/vulnerabilities relative to the *booted* releasever.
* Anyway, given that we don't yet do etc merges on boot, it shouldn't be too
* common for users to stay long on e.g. f26 when they have f27 already deployed
* and ready to reboot into. */
g_autoptr(OstreeDeployment) cfg_merge_deployment =
ostree_sysroot_get_merge_deployment (sysroot, self->osname);
g_autofree char *source_root =
rpmostree_get_deployment_root (sysroot, cfg_merge_deployment);
if (!rpmostree_context_setup (ctx, NULL, source_root, NULL, cancellable, error))
return FALSE;
/* we always want to force a refetch of the metadata */
dnf_context_set_cache_age (rpmostree_context_get_dnf (ctx), 0);
/* point libdnf to our repos dir */
rpmostree_context_configure_from_deployment (ctx, sysroot, cfg_merge_deployment);
if (!rpmostree_context_download_metadata (ctx, cancellable, error))
return FALSE;
}
/* Note early return */
return TRUE;
}
RpmOstreeSysrootUpgraderLayeringType layering_type;
gboolean layering_changed = FALSE;
if (!rpmostree_sysroot_upgrader_prep_layering (upgrader, &layering_type, &layering_changed,

View File

@ -19,6 +19,7 @@
#pragma once
#include "rpmostreed-types.h"
#include "rpmostreed-daemon.h"
#include <gio/gunixfdlist.h>
@ -56,6 +57,7 @@ typedef enum {
RPMOSTREE_TRANSACTION_DEPLOY_FLAG_NO_OVERRIDES = (1 << 6),
RPMOSTREE_TRANSACTION_DEPLOY_FLAG_CACHE_ONLY = (1 << 7),
RPMOSTREE_TRANSACTION_DEPLOY_FLAG_DOWNLOAD_ONLY = (1 << 8),
RPMOSTREE_TRANSACTION_DEPLOY_FLAG_DOWNLOAD_METADATA_ONLY = (1 << 9),
} RpmOstreeTransactionDeployFlags;

View File

@ -0,0 +1,128 @@
/***
This file was originally part of systemd.
Copyright 2010 Lennart Poettering
systemd 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.1 of the License, or
(at your option) any later version.
systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <glib.h>
#include <sys/time.h>
#include "libsd-time-util.h"
static clockid_t map_clock_id(clockid_t c) {
/* Some more exotic archs (s390, ppc, …) lack the "ALARM" flavour of the clocks. Thus, clock_gettime() will
* fail for them. Since they are essentially the same as their non-ALARM pendants (their only difference is
* when timers are set on them), let's just map them accordingly. This way, we can get the correct time even on
* those archs. */
switch (c) {
case CLOCK_BOOTTIME_ALARM:
return CLOCK_BOOTTIME;
case CLOCK_REALTIME_ALARM:
return CLOCK_REALTIME;
default:
return c;
}
}
static usec_t timespec_load(const struct timespec *ts) {
g_assert(ts);
if (ts->tv_sec < 0 || ts->tv_nsec < 0)
return USEC_INFINITY;
if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
return USEC_INFINITY;
return
(usec_t) ts->tv_sec * USEC_PER_SEC +
(usec_t) ts->tv_nsec / NSEC_PER_USEC;
}
static usec_t now(clockid_t clock_id) {
struct timespec ts;
g_assert_cmpint (clock_gettime(map_clock_id(clock_id), &ts), ==, 0);
return timespec_load(&ts);
}
char *libsd_format_timestamp_relative(char *buf, size_t l, usec_t t) {
const char *s;
usec_t n, d;
if (t <= 0 || t == USEC_INFINITY)
return NULL;
n = now(CLOCK_REALTIME);
if (n > t) {
d = n - t;
s = "ago";
} else {
d = t - n;
s = "left";
}
if (d >= USEC_PER_YEAR)
snprintf(buf, l, USEC_FMT " years " USEC_FMT " months %s",
d / USEC_PER_YEAR,
(d % USEC_PER_YEAR) / USEC_PER_MONTH, s);
else if (d >= USEC_PER_MONTH)
snprintf(buf, l, USEC_FMT " months " USEC_FMT " days %s",
d / USEC_PER_MONTH,
(d % USEC_PER_MONTH) / USEC_PER_DAY, s);
else if (d >= USEC_PER_WEEK)
snprintf(buf, l, USEC_FMT " weeks " USEC_FMT " days %s",
d / USEC_PER_WEEK,
(d % USEC_PER_WEEK) / USEC_PER_DAY, s);
else if (d >= 2*USEC_PER_DAY)
snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s);
else if (d >= 25*USEC_PER_HOUR)
snprintf(buf, l, "1 day " USEC_FMT "h %s",
(d - USEC_PER_DAY) / USEC_PER_HOUR, s);
else if (d >= 6*USEC_PER_HOUR)
snprintf(buf, l, USEC_FMT "h %s",
d / USEC_PER_HOUR, s);
else if (d >= USEC_PER_HOUR)
snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s",
d / USEC_PER_HOUR,
(d % USEC_PER_HOUR) / USEC_PER_MINUTE, s);
else if (d >= 5*USEC_PER_MINUTE)
snprintf(buf, l, USEC_FMT "min %s",
d / USEC_PER_MINUTE, s);
else if (d >= USEC_PER_MINUTE)
snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s",
d / USEC_PER_MINUTE,
(d % USEC_PER_MINUTE) / USEC_PER_SEC, s);
else if (d >= USEC_PER_SEC)
snprintf(buf, l, USEC_FMT "s %s",
d / USEC_PER_SEC, s);
else if (d >= USEC_PER_MSEC)
snprintf(buf, l, USEC_FMT "ms %s",
d / USEC_PER_MSEC, s);
else if (d > 0)
snprintf(buf, l, USEC_FMT"us %s",
d, s);
else
snprintf(buf, l, "now");
buf[l-1] = 0;
return buf;
}

View File

@ -0,0 +1,67 @@
#pragma once
/***
This file was originally part of systemd.
Copyright 2010 Lennart Poettering
systemd 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.1 of the License, or
(at your option) any later version.
systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
typedef uint64_t usec_t;
typedef uint64_t nsec_t;
#define PRI_NSEC PRIu64
#define PRI_USEC PRIu64
#define NSEC_FMT "%" PRI_NSEC
#define USEC_FMT "%" PRI_USEC
#define USEC_INFINITY ((usec_t) -1)
#define NSEC_INFINITY ((nsec_t) -1)
#define MSEC_PER_SEC 1000ULL
#define USEC_PER_SEC ((usec_t) 1000000ULL)
#define USEC_PER_MSEC ((usec_t) 1000ULL)
#define NSEC_PER_SEC ((nsec_t) 1000000000ULL)
#define NSEC_PER_MSEC ((nsec_t) 1000000ULL)
#define NSEC_PER_USEC ((nsec_t) 1000ULL)
#define USEC_PER_MINUTE ((usec_t) (60ULL*USEC_PER_SEC))
#define NSEC_PER_MINUTE ((nsec_t) (60ULL*NSEC_PER_SEC))
#define USEC_PER_HOUR ((usec_t) (60ULL*USEC_PER_MINUTE))
#define NSEC_PER_HOUR ((nsec_t) (60ULL*NSEC_PER_MINUTE))
#define USEC_PER_DAY ((usec_t) (24ULL*USEC_PER_HOUR))
#define NSEC_PER_DAY ((nsec_t) (24ULL*NSEC_PER_HOUR))
#define USEC_PER_WEEK ((usec_t) (7ULL*USEC_PER_DAY))
#define NSEC_PER_WEEK ((nsec_t) (7ULL*NSEC_PER_DAY))
#define USEC_PER_MONTH ((usec_t) (2629800ULL*USEC_PER_SEC))
#define NSEC_PER_MONTH ((nsec_t) (2629800ULL*NSEC_PER_SEC))
#define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC))
#define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC))
/* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this
* to 6. Let's rely on that. */
#define FORMAT_TIMESTAMP_MAX (3+1+10+1+8+1+6+1+6+1)
#define FORMAT_TIMESTAMP_WIDTH 28 /* when outputting, assume this width */
#define FORMAT_TIMESTAMP_RELATIVE_MAX 256
#define FORMAT_TIMESPAN_MAX 64
char *libsd_format_timestamp_relative(char *buf, size_t l, usec_t t);

View File

@ -46,10 +46,6 @@
#define RPMOSTREE_MESSAGE_PKG_REPOS SD_ID128_MAKE(0e,ea,67,9b,bf,a3,4d,43,80,2d,ec,99,b2,74,eb,e7)
#define RPMOSTREE_MESSAGE_PKG_IMPORT SD_ID128_MAKE(df,8b,b5,4f,04,fa,47,08,ac,16,11,1b,bf,4b,a3,52)
#define RPMOSTREE_DIR_CACHE_REPOMD "repomd"
#define RPMOSTREE_DIR_CACHE_SOLV "solv"
#define RPMOSTREE_DIR_LOCK "lock"
static OstreeRepo * get_pkgcache_repo (RpmOstreeContext *self);
/* Given a string, look for ostree:// or rojig:// prefix and

View File

@ -27,6 +27,10 @@
#include "libglnx.h"
#define RPMOSTREE_CORE_CACHEDIR "/var/cache/rpm-ostree/"
#define RPMOSTREE_DIR_CACHE_REPOMD "repomd"
#define RPMOSTREE_DIR_CACHE_SOLV "solv"
#define RPMOSTREE_DIR_LOCK "lock"
/* See http://lists.rpm.org/pipermail/rpm-maint/2017-October/006681.html */
#define RPMOSTREE_RPMDB_LOCATION "usr/share/rpm"
#define RPMOSTREE_SYSIMAGE_DIR "usr/lib/sysimage"

View File

@ -143,7 +143,7 @@ static char *
pkg_nevra_strdup (Header h1)
{
return rpmostree_header_custom_nevra_strdup (h1, PKG_NEVRA_FLAGS_NAME |
PKG_NEVRA_FLAGS_EPOCH_VERSION_RELEASE |
PKG_NEVRA_FLAGS_EVR |
PKG_NEVRA_FLAGS_ARCH);
}
@ -893,9 +893,9 @@ rpmostree_get_refts_for_commit (OstreeRepo *repo,
return TRUE;
}
static gint
pkg_array_compare (DnfPackage **p_pkg1,
DnfPackage **p_pkg2)
gint
rpmostree_pkg_array_compare (DnfPackage **p_pkg1,
DnfPackage **p_pkg2)
{
return dnf_package_cmp (*p_pkg1, *p_pkg2);
}
@ -915,7 +915,7 @@ rpmostree_sighandler_reset_cleanup (RpmSighandlerResetCleanup *cleanup)
static void
print_pkglist (GPtrArray *pkglist)
{
g_ptr_array_sort (pkglist, (GCompareFunc) pkg_array_compare);
g_ptr_array_sort (pkglist, (GCompareFunc) rpmostree_pkg_array_compare);
for (guint i = 0; i < pkglist->len; i++)
{
@ -1124,7 +1124,7 @@ GPtrArray*
rpmostree_sack_get_sorted_packages (DnfSack *sack)
{
g_autoptr(GPtrArray) pkglist = rpmostree_sack_get_packages (sack);
g_ptr_array_sort (pkglist, (GCompareFunc)pkg_array_compare);
g_ptr_array_sort (pkglist, (GCompareFunc)rpmostree_pkg_array_compare);
return g_steal_pointer (&pkglist);
}

View File

@ -114,6 +114,10 @@ rpmostree_get_refts_for_commit (OstreeRepo *repo,
GCancellable *cancellable,
GError **error);
gint
rpmostree_pkg_array_compare (DnfPackage **p_pkg1,
DnfPackage **p_pkg2);
void
rpmostree_print_transaction (DnfContext *context);

View File

@ -30,7 +30,7 @@
#include "rpmostree-util.h"
#include "rpmostree-origin.h"
#include "rpmostree-output.h"
#include "rpmostree.h"
#include "libsd-locale-util.h"
#include "libglnx.h"
#define RPMOSTREE_OLD_PKGCACHE_DIR "extensions/rpmostree/pkgcache"
@ -777,7 +777,7 @@ rpmostree_diff_print_formatted (GPtrArray *removed,
{
gboolean first;
g_assert (modified_old->len == modified_new->len);
g_assert_cmpuint (modified_old->len, ==, modified_new->len);
first = TRUE;
for (guint i = 0; i < modified_old->len; i++)
@ -842,6 +842,50 @@ rpmostree_diff_print_formatted (GPtrArray *removed,
}
}
static void
variant_diff_print_modified (guint max_key_len,
GVariant *modified,
const char *type)
{
guint n = g_variant_n_children (modified);
for (guint i = 0; i < n; i++)
{
const char *name, *evr_old, *evr_new;
g_variant_get_child (modified, i, "(u&s(&ss)(&ss))",
NULL, &name, &evr_old, NULL, &evr_new, NULL);
g_print (" %*s%s %s %s -> %s\n", max_key_len, i == 0 ? type : "", i == 0 ? ":" : " ",
name, evr_old, evr_new);
}
}
static void
variant_diff_print_singles (guint max_key_len,
GVariant *singles,
const char *type)
{
guint n = g_variant_n_children (singles);
for (guint i = 0; i < n; i++)
{
const char *name, *evr, *arch;
g_variant_get_child (singles, i, "(u&s&s&s)", NULL, &name, &evr, &arch);
g_print (" %*s%s %s-%s.%s\n", max_key_len, i == 0 ? type : "", i == 0 ? ":" : " ",
name, evr, arch);
}
}
void
rpmostree_variant_diff_print_formatted (guint max_key_len,
GVariant *upgraded,
GVariant *downgraded,
GVariant *removed,
GVariant *added)
{
variant_diff_print_modified (max_key_len, upgraded, "Upgraded");
variant_diff_print_modified (max_key_len, downgraded, "Downgraded");
variant_diff_print_singles (max_key_len, removed, "Removed");
variant_diff_print_singles (max_key_len, added, "Added");
}
static int
pkg_cmp_end (RpmOstreePackage *a, RpmOstreePackage *b)
{
@ -957,3 +1001,33 @@ rpmostree_variant_bsearch_str (GVariant *array,
*out_pos = imid;
return FALSE;
}
const char*
rpmostree_auto_update_policy_to_str (RpmostreedAutomaticUpdatePolicy policy,
GError **error)
{
switch (policy)
{
case RPMOSTREED_AUTOMATIC_UPDATE_POLICY_NONE:
return "none";
case RPMOSTREED_AUTOMATIC_UPDATE_POLICY_CHECK:
return "check";
default:
return glnx_null_throw (error, "Invalid policy value %u", policy);
}
}
gboolean
rpmostree_str_to_auto_update_policy (const char *str,
RpmostreedAutomaticUpdatePolicy *out_policy,
GError **error)
{
g_assert (str);
if (g_str_equal (str, "none") || g_str_equal (str, "off"))
*out_policy = RPMOSTREED_AUTOMATIC_UPDATE_POLICY_NONE;
else if (g_str_equal (str, "check"))
*out_policy = RPMOSTREED_AUTOMATIC_UPDATE_POLICY_CHECK;
else
return glnx_throw (error, "Invalid value for AutomaticUpdatePolicy: '%s'", str);
return TRUE;
}

View File

@ -26,6 +26,7 @@
#include <ostree.h>
#include <libdnf/libdnf.h>
#include "libglnx.h"
#include "rpmostree.h"
#define _N(single, plural, n) ( (n) == 1 ? (single) : (plural) )
#define _NS(n) _N("", "s", n)
@ -99,6 +100,13 @@ rpmostree_diff_print_formatted (GPtrArray *removed,
GPtrArray *modified_old,
GPtrArray *modified_new);
void
rpmostree_variant_diff_print_formatted (guint max_key_len,
GVariant *upgraded,
GVariant *downgraded,
GVariant *removed,
GVariant *added);
void
rpmostree_diff_print (GPtrArray *removed,
GPtrArray *added,
@ -191,3 +199,24 @@ gboolean
rpmostree_variant_bsearch_str (GVariant *array,
const char *str,
int *out_pos);
/* these are kept here for easier sharing with the client */
typedef enum {
RPMOSTREED_AUTOMATIC_UPDATE_POLICY_NONE,
RPMOSTREED_AUTOMATIC_UPDATE_POLICY_CHECK,
} RpmostreedAutomaticUpdatePolicy;
const char*
rpmostree_auto_update_policy_to_str (RpmostreedAutomaticUpdatePolicy policy,
GError **error);
gboolean
rpmostree_str_to_auto_update_policy (const char *str,
RpmostreedAutomaticUpdatePolicy *out_policy,
GError **error);
typedef enum {
RPM_OSTREE_PKG_TYPE_BASE,
RPM_OSTREE_PKG_TYPE_LAYER,
} RpmOstreePkgTypes;

View File

@ -487,20 +487,21 @@ $files
EOF
(cd $test_tmpdir/yumrepo/specs &&
rpmbuild -ba $name.spec \
--define "_topdir $PWD" \
--define "_sourcedir $PWD" \
--define "_specdir $PWD" \
--define "_builddir $PWD/.build" \
--define "_srcrpmdir $PWD" \
--define "_rpmdir $test_tmpdir/yumrepo/packages" \
--define "_buildrootdir $PWD")
(cd yumrepo && createrepo_c --no-database .)
if test '!' -f yumrepo.repo; then
cat > yumrepo.repo.tmp << EOF
(cd $test_tmpdir/yumrepo && createrepo_c --no-database .)
if test '!' -f $test_tmpdir/yumrepo.repo; then
cat > $test_tmpdir/yumrepo.repo.tmp << EOF
[test-repo]
name=test-repo
baseurl=file:///$PWD/yumrepo
EOF
mv yumrepo.repo{.tmp,}
mv $test_tmpdir/yumrepo.repo{.tmp,}
fi
}

View File

@ -48,7 +48,7 @@ vm_raw_rsync() {
vm_rsync() {
if ! test -f .vagrant/using_sshfs; then
pushd ${topsrcdir}
vm_raw_rsync --exclude .git/ . $VM:/var/roothome/sync
vm_raw_rsync --delete --exclude .git/ . $VM:/var/roothome/sync
popd
fi
}

View File

@ -0,0 +1,99 @@
/*
Given a ref, read its pkglist, inject it in a new commit that is for our
purposes identical to the one the ref is pointing to, then reset the ref to that
commit. Essentially, we replace the tip with a copy, except that it has the
pkglist metadata.
This is used by tests that test features that require the new pkglist metadata
and is also really useful for debugging.
*/
#include "config.h"
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib-unix.h>
#include "libglnx.h"
#include "rpmostree-rpm-util.h"
static gboolean
impl (const char *repo_path,
const char *refspec,
GError **error)
{
g_autofree char *remote = NULL;
g_autofree char *ref = NULL;
if (!ostree_parse_refspec (refspec, &remote, &ref, error))
return FALSE;
g_autoptr(OstreeRepo) repo = ostree_repo_open_at (AT_FDCWD, repo_path, NULL, error);
if (!repo)
return FALSE;
g_autofree char *checksum = NULL;
if (!ostree_repo_resolve_rev (repo, refspec, FALSE, &checksum, error))
return FALSE;
g_autoptr(GVariant) commit = NULL;
if (!ostree_repo_load_commit (repo, checksum, &commit, NULL, error))
return FALSE;
g_autoptr(GVariant) meta = g_variant_get_child_value (commit, 0);
g_autoptr(GVariantDict) meta_dict = g_variant_dict_new (meta);
if (g_variant_dict_contains (meta_dict, "rpmostree.rpmdb.pkglist"))
{
g_print ("Refspec '%s' already has pkglist metadata; exiting.\n", refspec);
return TRUE;
}
/* just an easy way to checkout the rpmdb */
g_autoptr(RpmOstreeRefSack) rsack =
rpmostree_get_refsack_for_commit (repo, checksum, NULL, error);
if (!rsack)
return FALSE;
g_assert (rsack->tmpdir.initialized);
g_autoptr(GVariant) pkglist = NULL;
if (!rpmostree_create_rpmdb_pkglist_variant (rsack->tmpdir.fd, ".", &pkglist, NULL, error))
return FALSE;
g_variant_dict_insert_value (meta_dict, "rpmostree.rpmdb.pkglist", pkglist);
g_autoptr(GVariant) new_meta = g_variant_ref_sink (g_variant_dict_end (meta_dict));
g_autoptr(GFile) root = NULL;
if (!ostree_repo_read_commit (repo, checksum, &root, NULL, NULL, error))
return FALSE;
g_autofree char *new_checksum = NULL;
g_autofree char *parent = ostree_commit_get_parent (commit);
if (!ostree_repo_write_commit (repo, parent, "", "", new_meta, OSTREE_REPO_FILE (root),
&new_checksum, NULL, error))
return FALSE;
if (!ostree_repo_set_ref_immediate (repo, remote, ref, new_checksum, NULL, error))
return FALSE;
g_print("%s => %s\n", refspec, new_checksum);
return TRUE;
}
int
main (int argc, char *argv[])
{
if (argc != 3)
errx (EXIT_FAILURE, "Usage: %s <repo> <refspec>", argv[0]);
const char *repo_path = argv[1];
const char *refspec = argv[2];
g_autoptr(GError) local_error = NULL;
if (!impl (repo_path, refspec, &local_error))
errx (EXIT_FAILURE, "%s", local_error->message);
g_assert (local_error == NULL);
return EXIT_SUCCESS;
}

View File

@ -12,6 +12,26 @@ if test -z "${INSIDE_VM:-}"; then
fi
vm_rsync
# ✀✀✀ BEGIN selinux-policy hack (part 1) for
# https://github.com/fedora-selinux/selinux-policy-contrib/pull/45
selhack=selinux-tmp-hack
if ! vm_cmd sesearch -A -s init_t -t install_t -c dbus | grep -q allow; then
echo "Activating selinux-tmp-hack"
d=$(mktemp -d)
cat > $d/$selhack.te << 'EOF'
policy_module(selinux-tmp-hack, 1.0.0)
gen_require(`
type install_t;
')
init_dbus_chat(install_t)
EOF
make -C $d -f /usr/share/selinux/devel/Makefile $selhack.pp
vm_send /var/roothome/sync $d/$selhack.pp
rm -rf $d
fi
# ✀✀✀ END selinux-policy hack ✀✀✀
vm_cmd env INSIDE_VM=1 /var/roothome/sync/tests/vmcheck/overlay.sh
vm_reboot
exit 0
@ -54,6 +74,20 @@ INSTTREE=/var/roothome/sync/insttree
rsync -rlv $INSTTREE/usr/ vmcheck/usr/
rsync -rlv $INSTTREE/etc/ vmcheck/usr/etc/
## ✀✀✀ BEGIN selinux-policy hack (part 2) for
## https://github.com/fedora-selinux/selinux-policy-contrib/pull/45
selhack=selinux-tmp-hack
pp=/var/roothome/sync/$selhack.pp
if [ -f $pp ]; then
seld=usr/share/selinux/packages/$selhack
mkdir -p vmcheck/$seld
cp $pp vmcheck/$seld
mkdir vmcheck/var/tmp # bwrap wrapper will mount tmpfs there
/var/roothome/sync/scripts/bwrap-script-shell.sh /ostree/repo/tmp/vmcheck \
semodule -v -n -i /$seld/$selhack.pp
fi
## ✀✀✀ END selinux-policy hack ✀✀✀
# ✀✀✀ BEGIN hack to get --keep-metadata
if ! ostree commit --help | grep -q -e --keep-metadata; then
# this is fine, rsync doesn't modify in place
@ -63,7 +97,14 @@ if ! ostree commit --help | grep -q -e --keep-metadata; then
fi
# ✀✀✀ END hack to get --keep-metadata ✀✀✀
# if the commit already has pkglist metadata (i.e. the tree was composed with at
# least v2018.1), make sure it gets preserved, because it's useful for playing
# around (but note it's not a requirement for our tests)
commit_opts=
if ostree show $commit --raw | grep -q rpmostree.rpmdb.pkglist; then
commit_opts="${commit_opts} --keep-metadata=rpmostree.rpmdb.pkglist"
fi
source_opt= # make this its own var since it contains spaces
if [ $origin != vmcheck ]; then
source_title="${origin}"
@ -82,4 +123,5 @@ fi
ostree commit --parent=$commit -b vmcheck --consume --no-bindings \
--link-checkout-speedup ${commit_opts} "${source_opt}" \
--selinux-policy=vmcheck --tree=dir=vmcheck
ostree admin deploy vmcheck

186
tests/vmcheck/test-autoupdate.sh Executable file
View File

@ -0,0 +1,186 @@
#!/bin/bash
#
# Copyright (C) 2018 Jonathan Lebon
#
# 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.
set -euo pipefail
. ${commondir}/libtest.sh
. ${commondir}/libvm.sh
set -x
# first, let's make sure the timer is disabled so it doesn't mess up with our
# tests
vm_cmd systemctl disable --now rpm-ostreed-automatic.timer
# Really testing this like a user requires a remote ostree server setup.
# Let's start by setting up the repo.
REMOTE_OSTREE=/ostree/repo/tmp/vmcheck-remote
vm_cmd mkdir -p $REMOTE_OSTREE
vm_cmd ostree init --repo=$REMOTE_OSTREE --mode=archive
vm_start_httpd ostree_server $REMOTE_OSTREE 8888
# We need to build up a history on the server. Rather than wasting time
# composing trees for real, we just use client package layering to create new
# trees that we then "lift" into the server before cleaning them up client-side.
# steal a commit from the system repo and make a branch out of it
lift_commit() {
checksum=$1; shift
branch=$1; shift
vm_cmd ostree pull-local --repo=$REMOTE_OSTREE --disable-fsync \
/ostree/repo $checksum
vm_cmd ostree --repo=$REMOTE_OSTREE refs $branch --delete
vm_cmd ostree --repo=$REMOTE_OSTREE refs $checksum --create=$branch
}
# use a previously stolen commit to create an update on our vmcheck branch,
# complete with version string and pkglist metadata
create_update() {
branch=$1; shift
vm_cmd ostree commit --repo=$REMOTE_OSTREE -b vmcheck \
--tree=ref=$branch --add-metadata-string=version=$branch --fsync=no
# avoid libtool wrapper here since we're running on the VM and it would try to
# cd to topsrcdir/use gcc; libs are installed anyway
vm_cmd /var/roothome/sync/.libs/inject-pkglist $REMOTE_OSTREE vmcheck
}
# (delete ref but don't prune for easier debugging)
vm_cmd ostree refs --repo=$REMOTE_OSTREE vmcheck --delete
# now let's build some pkgs that we'll jury-rig into a base update
# this whole block can be commented out for a speed-up when iterating locally
vm_build_rpm base-pkg-foo version 1.4 release 7
vm_build_rpm base-pkg-bar
vm_build_rpm base-pkg-baz version 1.1 release 1
vm_rpmostree install base-pkg-{foo,bar,baz}
lift_commit $(vm_get_pending_csum) v1
vm_rpmostree cleanup -p
rm -rf $test_tmpdir/yumrepo
vm_build_rpm base-pkg-foo version 1.4 release 8 # upgraded
vm_build_rpm base-pkg-bar version 0.9 release 3 # downgraded
vm_build_rpm base-pkg-boo version 3.7 release 2.11 # added
vm_rpmostree install base-pkg-{foo,bar,boo}
lift_commit $(vm_get_pending_csum) v2
vm_rpmostree cleanup -p
# ok, we're done with prep, now let's rebase on the first revision and install a
# layered package
create_update v1
vm_cmd ostree remote add vmcheckmote --no-gpg-verify http://localhost:8888/
vm_build_rpm layered-cake version 2.1 release 3
vm_rpmostree rebase vmcheckmote:vmcheck --install layered-cake
vm_reboot
vm_rpmostree status -v
vm_assert_status_jq \
".deployments[0][\"origin\"] == \"vmcheckmote:vmcheck\"" \
".deployments[0][\"version\"] == \"v1\"" \
'.deployments[0]["packages"]|length == 1' \
'.deployments[0]["packages"]|index("layered-cake") >= 0'
echo "ok prep"
# start it up again since we rebooted
vm_start_httpd ostree_server $REMOTE_OSTREE 8888
change_policy() {
policy=$1; shift
vm_cmd cp /usr/etc/rpm-ostreed.conf /etc
cat > tmp.sh << EOF
echo -e "[Daemon]\nAutomaticUpdatePolicy=$policy" > /etc/rpm-ostreed.conf
EOF
vm_cmdfile tmp.sh
vm_rpmostree reload
}
# make sure that off means off
change_policy off
vm_rpmostree status | grep 'auto updates disabled'
vm_rpmostree upgrade --trigger-automatic-update-policy > out.txt
assert_file_has_content out.txt "Automatic updates are not enabled; exiting"
echo "ok disabled"
# ok, let's test out check
change_policy check
vm_rpmostree status | grep 'auto updates enabled (check'
# build an *older version* and check that we don't report an update
vm_build_rpm layered-cake version 2.1 release 2
vm_rpmostree upgrade --trigger-automatic-update-policy
vm_rpmostree status -v > out.txt
assert_not_file_has_content out.txt "Available update"
# build a *newer version* and check that we report an update
vm_build_rpm layered-cake version 2.1 release 4
vm_rpmostree upgrade --trigger-automatic-update-policy
vm_rpmostree status > out.txt
assert_file_has_content out.txt "Available update"
assert_file_has_content out.txt "Diff: 1 upgraded"
vm_rpmostree status -v > out.txt
assert_file_has_content out.txt "Upgraded: layered-cake 2.1-3 -> 2.1-4"
# make sure we don't report ostree-based stuff somehow
! grep -A999 'Available update' out.txt | grep "Version"
! grep -A999 'Available update' out.txt | grep "Timestamp"
! grep -A999 'Available update' out.txt | grep "Commit"
echo "ok check mode layered only"
# ok now let's add ostree updates in the picture
create_update v2
vm_rpmostree upgrade --trigger-automatic-update-policy
# make sure we only pulled down the commit metadata
if vm_cmd ostree checkout vmcheckmote:vmcheck --subpath /usr/share/rpm; then
assert_not_reached "Was able to checkout /usr/share/rpm?"
fi
assert_update() {
vm_assert_status_jq \
'.["cached-update"]["origin"] == "vmcheckmote:vmcheck"' \
'.["cached-update"]["version"] == "v2"' \
'.["cached-update"]["ref-has-new-commit"] == true' \
'.["cached-update"]["gpg-enabled"] == false'
# we could assert more json here, though how it's presented to users is
# important, and implicitly tests the json
vm_rpmostree status > out.txt
assert_file_has_content out.txt 'Diff: 2 upgraded, 1 downgraded, 1 removed, 1 added'
vm_rpmostree status -v > out.txt
assert_file_has_content out.txt 'Upgraded: base-pkg-foo 1.4-7 -> 1.4-8'
assert_file_has_content out.txt " layered-cake 2.1-3 -> 2.1-4"
assert_file_has_content out.txt 'Downgraded: base-pkg-bar 1.0-1 -> 0.9-3'
assert_file_has_content out.txt 'Removed: base-pkg-baz-1.1-1.x86_64'
assert_file_has_content out.txt 'Added: base-pkg-boo-3.7-2.11.x86_64'
}
assert_update
echo "ok check mode ostree"
assert_default_deployment_is_update() {
vm_assert_status_jq \
'.deployments[0]["origin"] == "vmcheckmote:vmcheck"' \
'.deployments[0]["version"] == "v2"' \
'.deployments[0]["packages"]|length == 1' \
'.deployments[0]["packages"]|index("layered-cake") >= 0'
vm_rpmostree db list $(vm_get_pending_csum) > list.txt
assert_file_has_content list.txt 'layered-cake-2.1-4.x86_64'
}
# now let's upgrade and check that it matches what we expect
vm_rpmostree upgrade
assert_default_deployment_is_update
echo "ok upgrade"

View File

@ -76,6 +76,8 @@ if vm_cmd test -f /etc/rpm-ostreed.conf; then
fi
vm_cmd cp -f /usr/etc/rpm-ostreed.conf /etc
vm_cmd ostree remote delete --if-exists vmcheckmote
origdir=$(pwd)
echo -n '' > ${LOG}
@ -186,6 +188,7 @@ for tf in $(find . -name 'test-*.sh' | sort); do
# and clean up any leftovers from our tmp
osname=$(vm_get_booted_deployment_info osname)
vm_cmd rm -rf /ostree/deploy/$osname/var/tmp/vmcheck
vm_cmd ostree remote delete --if-exists vmcheckmote
done